diff --git a/.gitignore b/.gitignore index edab63e..1189536 100644 --- a/.gitignore +++ b/.gitignore @@ -20,5 +20,10 @@ composer.lock # Zip artifacts (built by CI) *.zip +# Environment & credentials +.env +.env.local +.env.credentials + # AI agent context (local only, not deployed) .claude/ diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index df08b58..4091257 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -2,384 +2,206 @@ ## Overview -Peptide Starter (peptide-starter) is a premium, accessible WordPress theme for peptiderepo.com — a scientific peptide reference database. The theme is a classic (non-block) WordPress theme that assembles a modern, responsive frontend by combining a monolithic CSS architecture with vanilla JavaScript. It integrates seamlessly with three companion plugins (Peptide Search AI, Peptide News, PRAutoBlogger) via shortcodes, coordinates dark mode across the ecosystem via CSS custom properties and the DOM `data-theme` attribute, and provides customizable hero and footer sections through the WordPress Customizer. +Peptide Starter (peptide-starter) is a premium, accessible WordPress theme for peptiderepo.com — a scientific peptide reference database. The theme is a classic (non-block) WordPress theme that assembles a modern, responsive frontend by combining a monolithic CSS architecture with vanilla JavaScript. It integrates seamlessly with five companion plugins (Peptide Search AI, Peptide News, PRAutoBlogger, Peptide Tools, Peptide Tracker, Peptide Community) via shortcodes, coordinates dark mode across the ecosystem via CSS custom properties and the DOM `data-theme` attribute, and provides customizable hero, navigation, authentication, and footer sections. ## Directory Structure ``` peptide-starter/ -├── style.css # All theme CSS (~44KB). Architecture: reset → tokens → typography → -│ # globals → components → regions → pages → animations → a11y → dark mode → responsive +├── style.css # All theme CSS. Architecture: reset → tokens → typography → +│ # globals → components → regions → pages → animations → +│ # a11y → dark mode → responsive → new feature sections │ -├── functions.php # Theme setup (430 lines) +├── functions.php # Theme setup entry point (~206 lines) +│ # - Constants, feature module loading │ # - Theme support registration -│ # - Script/style enqueueing with wp_localize_script -│ # - 4x footer widget area registration -│ # - Customizer settings (hero, footer, dark mode) -│ # - Navigation Menu Walker for active states -│ # - Helper functions (get_hero_title, get_footer_copyright, etc.) -│ # - Inline dark mode script in wp_head (for flicker-free initialization) +│ # - Script/style enqueueing (incl conditional) +│ # - Widget areas, newsletter handler, dark mode inline script │ -├── header.php # Global header region (131 lines) -│ # - Site logo/branding -│ # - Primary navigation menu with fallback -│ # - Header icons (search, dark mode toggle, cart, mobile menu) -│ # - Search overlay for Peptide Search AI plugin -│ # - Nav overlay for mobile menu +├── inc/ +│ ├── helpers.php # Nav walker, custom logo, hero/footer getters, pagination +│ ├── customizer.php # Customizer sections, settings, sanitize callbacks +│ ├── auth-handlers.php # AJAX login + registration handlers +│ ├── contact-handler.php # AJAX contact form handler (settings panel) +│ ├── page-setup.php # Auto-create pages on theme activation +│ └── newsletter-admin.php # Admin page to view/export subscriber emails │ -├── footer.php # Global footer region (81 lines) -│ # - Newsletter signup form (conditionally rendered) -│ # - 4-column footer widget grid -│ # - Default content fallback per column -│ # - Footer copyright text (from Customizer) +├── header.php # Global header (~190 lines) +│ # - Site logo, primary navigation with dropdowns +│ # - Sign In / User menu (state-aware) +│ # - Settings icon, language placeholder +│ # - Search, dark mode toggle, cart, mobile menu +│ # - Search overlay, fallback nav │ -├── front-page.php # Home page template (86 lines) -│ # - Hero section (title, subtitle, search) -│ # - PRAutoBlogger posts widget [prautoblogger_posts] -│ # - Peptide News feed section [peptide_news] -│ # - Shortcode integration with graceful degradation +├── footer.php # Global footer (~100 lines) +│ # - Newsletter signup (front page via template part) +│ # - 4-column widget grid with updated defaults +│ # - Copyright, disclaimer, language links +│ # - Settings panel include │ -├── page.php # Generic page template (53 lines) -│ # - Page header with title -│ # - Article container -│ # - Edit link display +├── front-page.php # Home page template (~100 lines) +│ # - Hero section (title, subtitle, search, CTAs) +│ # - Research Modules grid (6 cards) +│ # - [prautoblogger_posts] shortcode +│ # - [peptide_news] shortcode │ -├── index.php # Fallback template (27 lines) -│ # - Loop with dynamic post type content dispatch -│ # - Pagination via peptide_starter_pagination() +├── page-calculator.php # Calculator page template → [peptide_tools_calculator] +├── page-protocol-builder.php # Protocol Builder template → [peptide_tools_protocol_builder] +├── page-tracker.php # Tracker template → [peptide_tracker] +├── page-subject-log.php # Subject Log template → [peptide_tracker_subject_log] +├── page-documentation.php # Documentation/SOP page — two-column layout with ToC sidebar +├── page-directory.php # Peptide Directory template → [peptide_directory] +├── page-science-feed.php # Science Feed template → [peptide_news] + newsletter +├── page-profile.php # User Profile template → [peptide_community_profile] +├── page-auth.php # Sign In / Register — branded auth forms with AJAX │ +├── page.php # Generic page template +├── index.php # Fallback template ├── single-peptide.php # Peptide detail page (CPT) -│ # - Renders single peptide post -│ ├── archive-peptide.php # Peptide archive (CPT grid) -│ # - Displays peptide CPT items in grid layout -│ ├── 404.php # 404 error page -│ # - Default 404 handling -│ ├── searchform.php # Search form template -│ # - Standard WordPress search form │ ├── template-parts/ -│ ├── content.php # Default post/page loop item (19 lines) -│ # - Title, date, author, excerpt, read more link -│ +│ ├── content.php # Default post/page loop item │ ├── content-peptide.php # Peptide CPT card template -│ # - Peptide-specific card styling with .ps-card +│ ├── content-none.php # Empty state template +│ ├── module-cards.php # Research Modules 6-card grid (front page) +│ ├── newsletter-signup.php # Reusable newsletter signup section +│ └── settings-panel.php # Slide-out support/contact panel │ -│ └── content-none.php # Empty state template -│ # - Message when no posts found -│ -├── assets/ -│ └── js/ -│ ├── navigation.js # Mobile menu toggle (85 lines) -│ # - Mobile hamburger menu state management -│ # - Nav overlay click handling -│ # - Escape key, resize, link click closing -│ # - aria-expanded attribute updates -│ -│ └── theme.js # Dark mode + utilities (211 lines) -│ # - Dark mode toggle & localStorage persistence -│ # - System preference detection (prefers-color-scheme) -│ # - Custom 'themechange' event dispatch to plugins -│ # - Search overlay open/close functionality -│ # - Icon state toggling (sun/moon) -│ # - window.peptideStarterTheme API for plugin access -│ # - Smooth scroll + focus-visible polyfill +├── assets/js/ +│ ├── navigation.js # Mobile menu toggle (85 lines) +│ ├── theme.js # Dark mode + search overlay + utilities (210 lines) +│ ├── documentation.js # ToC generation + scroll spy for docs page (145 lines) +│ ├── auth.js # Auth form toggle, validation, AJAX (192 lines) +│ └── settings-panel.js # Settings panel open/close + contact form (163 lines) │ ├── languages/ # Translation files (.pot, .po, .mo) -│ # - Text domain: peptide-starter -│ -├── tests/ -│ ├── bootstrap.php # PHPUnit bootstrap — loads WP test framework + theme -│ ├── test-functions.php # Tests for helper functions (hero, footer, newsletter, sanitize) -│ └── test-theme-setup.php # Tests for theme supports, menus, widgets, scripts -│ -├── .phpcs.xml.dist # PHPCS config — WordPress-Core standard -├── phpunit.xml # PHPUnit config — points to tests/ directory -├── composer.json # Dev dependencies (PHPUnit, PHPCS, WPCS) -├── README.md # User-facing documentation -├── CLAUDE.md # Developer context notes +├── tests/ # PHPUnit tests +├── .phpcs.xml.dist # PHPCS config +├── phpunit.xml # PHPUnit config +├── composer.json # Dev dependencies +├── README.md # User-facing docs +├── CLAUDE.md # Developer context ├── CHANGELOG.md # Version history -└── screenshot.png # WordPress theme preview image +└── screenshot.png # Theme preview image ``` +## Navigation Structure + +``` +Home | Tools ▾ | My Data ▾ | Resources ▾ | [Sign In / Username ▾] ⚙ 🌐 🔍 🌙 + +Tools dropdown: + ├── Calculator → /calculator + ├── Protocol Builder → /protocol-builder + └── Tracker → /tracker + +My Data dropdown: + ├── Peptides → /peptides + └── Subject Log → /subject-log + +Resources dropdown: + ├── Documentation → /documentation + └── Science Feed → /news + +User menu (logged in): + ├── My Profile → /profile + ├── Tracker → /tracker + ├── Subject Log → /subject-log + └── Sign Out +``` + +## Page Template Map + +| Page Title | Slug | Template File | Plugin Shortcode | +|------------|------|---------------|-----------------| +| Calculator | calculator | page-calculator.php | [peptide_tools_calculator] | +| Protocol Builder | protocol-builder | page-protocol-builder.php | [peptide_tools_protocol_builder] | +| Tracker | tracker | page-tracker.php | [peptide_tracker] | +| Subject Log | subject-log | page-subject-log.php | [peptide_tracker_subject_log] | +| Documentation | documentation | page-documentation.php | (WordPress content + JS ToC) | +| Peptide Directory | peptides | page-directory.php | [peptide_directory] | +| Science Feed | news | page-science-feed.php | [peptide_news] | +| Profile | profile | page-profile.php | [peptide_community_profile] | +| Sign In | auth | page-auth.php | (Custom auth forms) | + +Pages are auto-created on theme activation via `inc/page-setup.php`. + ## Data Flow & Architecture Diagrams -### Front Page Assembly (Plugin Integration) +### Front Page Assembly ``` front-page.php + ├─→ Hero Section (title, subtitle, search, CTAs) + │ └─→ [peptide_search] shortcode (Peptide Search AI plugin) │ - ├─→ Hero Section (title, subtitle, search) - │ │ - │ └─→ [peptide_search] shortcode - │ └─→ Peptide Search AI plugin (if active) + ├─→ Research Modules Grid (template-parts/module-cards.php) + │ └─→ 6 cards linking to tool/section pages + │ └─→ Checks shortcode_exists() for graceful degradation │ - ├─→ Hero CTAs (Browse Peptides, Learn More) + ├─→ [prautoblogger_posts] shortcode (PRAutoBlogger plugin) │ - ├─→ [prautoblogger_posts] shortcode - │ └─→ PRAutoBlogger plugin - │ └─→ Renders 6 auto-generated research articles + └─→ [peptide_news] shortcode (Peptide News plugin) +``` + +### Authentication Flow + +``` +page-auth.php + ├─→ Sign In form → auth.js AJAX → inc/auth-handlers.php + │ └─→ wp_signon() → redirect to original page │ - └─→ [peptide_news] shortcode - └─→ Peptide News plugin - └─→ Renders news feed grid + └─→ Register form → auth.js AJAX → inc/auth-handlers.php + └─→ wp_create_user() → auto-login → redirect to /profile ``` ### Dark Mode Coordination ``` -functions.php (wp_head) - │ - └─→ Inline script runs first (before CSS loads) - └─→ Sets html[data-theme] from: - 1. localStorage (user's choice) - 2. prefers-color-scheme (system) - 3. dark_mode_default (Customizer setting) - └─→ Prevents flash of wrong color on page load - │ - ├─→ style.css reads data-theme - │ └─→ @media selector: html[data-theme="dark"] - │ - └─→ theme.js detects theme changes - └─→ Dispatches 'themechange' event - └─→ Updates localStorage - └─→ Updates icon visibility - └─→ Plugins listen & update own styles -``` - -### Template Hierarchy & Routing - -``` -WordPress Request - │ - ├─→ is_front_page() ? front-page.php - │ └─→ Hero + [prautoblogger_posts] + [peptide_news] - │ - ├─→ is_singular('peptide') ? single-peptide.php - │ └─→ get_template_part('content-peptide') - │ - ├─→ is_archive() ? archive-peptide.php - │ └─→ Grid of peptide cards - │ - ├─→ is_page() ? page.php - │ └─→ get_template_part('content') - │ - ├─→ is_search() || is_category() || is_tag() ? index.php - │ └─→ Loop: get_template_part('content', get_post_type()) - │ └─→ Dispatches to content.php or content-{type}.php - │ - ├─→ is_404() ? 404.php - │ - └─→ Default ? index.php - └─→ Fallback loop template -``` - -### Script Loading & Dark Mode API - -``` -wp_enqueue_scripts (functions.php) - │ - ├─→ peptide-starter-style - │ └─→ style.css (PEPTIDE_STARTER_VERSION) - │ - ├─→ peptide-starter-navigation - │ └─→ assets/js/navigation.js (in footer, async) - │ - └─→ peptide-starter-theme - ├─→ assets/js/theme.js (in footer, async) - └─→ wp_localize_script('peptideStarterData') - ├─→ siteUrl: home_url('/') - └─→ isDarkMode: get_theme_mod('dark_mode_default') -``` - -### Plugin Dark Mode Listening - -Plugins can coordinate with the theme via three mechanisms: - -```javascript -// 1. Custom event (recommended) -document.addEventListener('themechange', (e) => { - console.log('Theme changed to:', e.detail.theme); // 'dark' | 'light' -}); - -// 2. DOM attribute mutation -const observer = new MutationObserver(() => { - const theme = document.documentElement.getAttribute('data-theme'); -}); -observer.observe(document.documentElement, { attributes: true }); - -// 3. CSS custom properties (theme.js updates these) -const color = getComputedStyle(document.documentElement).getPropertyValue('--text-primary'); +functions.php (wp_head inline script) + └─→ Sets html[data-theme] from localStorage → system pref → customizer default + ├─→ style.css reads data-theme for color overrides + └─→ theme.js dispatches 'themechange' event to plugins ``` ## External Integrations ### Plugin Integrations (via Shortcodes) -1. **Peptide Search AI** (`[peptide_search]`) - - Location: Hero section (header.php, front-page.php) - - Fallback: Standard WordPress search form - - Dark mode: Listens to `themechange` event - - Namespace: `.psa-*` +1. **Peptide Search AI** (`[peptide_search]`) — Hero + search overlay. Namespace: `.psa-*` +2. **Peptide News** (`[peptide_news]`) — Front page + science feed. Namespace: `.pn-*` +3. **PRAutoBlogger** (`[prautoblogger_posts]`) — Front page articles. Namespace: `.prab-*` +4. **Peptide Tools** (`[peptide_tools_calculator]`, `[peptide_tools_protocol_builder]`) — Tool pages +5. **Peptide Tracker** (`[peptide_tracker]`, `[peptide_tracker_subject_log]`) — Tracker pages +6. **Peptide Community** (`[peptide_community_profile]`, `[peptide_directory]`) — Profile + directory -2. **Peptide News** (`[peptide_news]`) - - Location: Front page (front-page.php) - - Renders: News feed grid with article cards - - Dark mode: Listens to `themechange` event - - Namespace: `.pn-*` - - Override: `functions.php` line 80-90 removes `-webkit-line-clamp` on excerpts - -3. **PRAutoBlogger** (`[prautoblogger_posts]`) - - Location: Front page (front-page.php) - - Renders: 6 auto-generated research articles section - - Dark mode: Listens to `themechange` event - - Namespace: `.prab-*` - -All plugins are checked with `shortcode_exists()` for graceful degradation. - -### WooCommerce Integration - -- Cart icon in header (header.php line 68) -- Conditionally rendered if `class_exists('WooCommerce')` -- Minimal theme styling; WooCommerce handles its own UI -- Icon: SVG shopping cart - -### Google Fonts Integration - -- Font: **Inter** (from Google Fonts) -- Loaded via CSS `@import url('...')` -- Fallback: System fonts (`-apple-system, BlinkMacSystemFont, etc.`) -- Display: `display=swap` for optimal loading - -### REST API & Customizer - -- Theme mods saved to `wp_options` table -- Customizer sections: Branding, Hero Section, Footer, Theme Mode -- All settings use `'transport' => 'postMessage'` for instant preview (except dark_mode_default which uses refresh) +All plugins checked with `shortcode_exists()` for graceful degradation. ## Key Architectural Decisions -### 1. Classic Theme (Not FSE/Block Theme) +1. **Classic Theme (Not FSE)** — Stability, control, plugin compatibility +2. **Monolithic CSS** — No build step, CSS custom properties for abstraction +3. **Vanilla JavaScript** — No bundler, direct browser ES6+, `node --check` validation +4. **Shortcode Integration** — Decouples plugins from theme via `shortcode_exists()` +5. **CSS Custom Properties for Dark Mode** — Single source of truth for colors +6. **No `!important` Policy** — Explicit specificity over cascade tricks +7. **Inline Dark Mode Script** — Prevents flash of wrong theme on load +8. **Split functions.php into inc/** — Keeps all files under 300 lines -**Why:** Peptide Repo predates WordPress Full Site Editing. Classic themes provide: -- Simpler, more stable template hierarchy for developers -- Full control over HTML structure without block markup -- Easier plugin compatibility without block interdependencies -- Proven stability for production scientific database +## Security -### 2. Monolithic CSS (Not SASS/LESS) - -**Why:** -- No build step = instant deploy without compilation -- Single 44KB file loads fast (gzips to ~7KB) -- CSS custom properties (variables) provide all needed abstraction -- Theme can be edited directly on remote server if needed -- Reduces deployment complexity on Hostinger shared hosting - -### 3. Vanilla JavaScript (No Bundler) - -**Why:** -- Simple, maintainable code without build toolchain -- Direct browser compatibility (ES6+) -- Minimal overhead; only 296 lines total across 2 files -- Each script can be tested by running `node --check` syntax validation -- No dependencies = no supply chain risk - -### 4. Shortcode Integration (Not Direct Function Calls) - -**Why:** -- Plugins are independent; shortcodes decouple content from theme -- Shortcodes can be disabled/removed without theme breaking -- `shortcode_exists()` provides graceful degradation -- Theme doesn't need to know plugin implementation details -- Content (front-page.php) stays separate from plugin business logic - -### 5. CSS Custom Properties for Dark Mode & Theming - -**Why:** -- Single source of truth for all colors (defined in `:root`) -- Plugins can read theme colors via `getComputedStyle()` -- Easy to switch between light/dark without duplicate selectors -- No need for SASS mixins or preprocessor -- Future-proof: standard CSS, works in all modern browsers - -### 6. No `!important` Policy (Global Scale) - -**Why:** -- `!important` breaks plugin CSS specificity -- Forces explicit specificity through selector weight instead -- Theme respects plugin namespaces (`.pn-*`, `.psa-*`, `.prab-*`) -- Only exception: line 85 (necessary override of Peptide News line-clamp) -- Maintains CSS maintainability and plugin harmony - -### 7. Inline Dark Mode Script in `wp_head` - -**Why:** -- Runs *before* CSS loads, preventing white flash in dark mode -- Reads localStorage synchronously (no async needed here) -- Sets `data-theme` attribute before first paint -- Fallback chain: localStorage → system preference → customizer default -- Performance impact: minimal (~1ms execution) - -### 8. Navigation Walker with Active State Detection - -**Why:** -- Custom Walker class (line 243-300) detects current-menu-item -- Adds `.active` class for CSS styling (matches WordPress conventions) -- Enables dynamic nav styling without JS -- Integrates with WordPress menus natively -- Fallback function for sites with no primary menu - -### 9. Helper Functions for Customizer Output - -**Why:** -- `peptide_starter_get_*()` functions (lines 317-340) centralize theme mod retrieval -- Consistent defaults across templates -- Easy to update copy site-wide from one place -- Separates data layer from presentation - -## Security & Performance Considerations - -- **Escaping:** All output uses `esc_html()`, `esc_url()`, `esc_attr()` appropriately -- **Nonces:** Newsletter form uses `wp_nonce_field()` with CSRF verification in handler -- **Sanitize callbacks:** All Customizer settings have explicit `sanitize_callback` (text, kses, checkbox) -- **Form handling:** Newsletter signup routes through `admin_post` action with nonce + email validation -- **Permissions:** Check `is_active_sidebar()` before rendering widgets -- **Performance:** Script/style versions use `PEPTIDE_STARTER_VERSION` constant for cache busting -- **Accessibility:** Skip-to-main-content link (header.php line 20), WCAG AA color contrast, focus indicators - -## Testing & CI/CD - -- **CI pipeline** (`.github/workflows/deploy.yml`): - 1. PHP lint (`php -l`) on all PHP files - 2. PHPCS with WordPress-Core standard - 3. JS syntax check (`node --check`) on all JS files - 4. Deploy via rsync (only if validation passes) - 5. Post-deploy health check (HTTP 200) -- **Unit tests** (`tests/`): PHPUnit with WordPress test framework - - `test-functions.php` — helpers, sanitizers, filters - - `test-theme-setup.php` — theme supports, menus, widgets, scripts -- **rsync safety**: Dev-only files excluded from deploy (tests/, vendor/, composer.json, phpunit.xml, .phpcs.xml.dist) - -## Browser Support - -- Chrome/Edge (latest 2 versions) -- Firefox (latest 2 versions) -- Safari (latest 2 versions) -- iOS Safari 12+ -- Chrome Android +- CSRF nonces on all forms (auth, newsletter, contact) +- All input sanitized at boundary (`sanitize_text_field`, `sanitize_email`, `sanitize_textarea_field`) +- All output escaped (`esc_html`, `esc_attr`, `esc_url`, `wp_kses_post`) +- Login redirect validated with `wp_validate_redirect()` to prevent open redirects +- Auth form uses AJAX to avoid exposing credentials in URL +- Contact form topics restricted to allowlist ## Version -**Current:** v1.3.1 (defined in `style.css` header line 7 and `functions.php` line 14 constant) +**Current:** v1.5.0 -When releasing a new version, update both locations. - -## Related Documentation - -- **CONVENTIONS.md** — How to add templates, components, plugin integrations -- **CLAUDE.md** — Developer context and quick reference -- **README.md** — User-facing feature list and installation -- **CHANGELOG.md** — Release history +When releasing a new version, update `style.css` header (line 7) and `PEPTIDE_STARTER_VERSION` in `functions.php`. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..5338067 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,79 @@ +# Peptide Starter Theme + +WordPress theme for peptiderepo.com — a scientific peptide reference database. + +## Repository + +- **GitHub**: `peptiderepo/peptide-starter-theme` (private) +- **Branch**: `main` is the production branch +- **Live site**: https://peptiderepo.com +- **Hosting**: Hostinger shared hosting + +## Deploy + +Push to `main` triggers GitHub Actions → rsync via SSH to Hostinger. +No CI lint step — the deploy workflow (`deploy.yml`) runs rsync directly. +LiteSpeed cache is purged automatically after deploy. + +Theme path on server: `~/domains/peptiderepo.com/public_html/wp-content/themes/peptide-starter/` + +## Architecture + +This is a classic WordPress theme (no block/FSE). All styling is in `style.css` (no build step, no preprocessor). JavaScript is vanilla — no bundler. + +### Key files + +| File | Purpose | +|------|---------| +| `style.css` | All theme CSS (~44KB). Organized: reset → tokens → typography → globals → components → regions → pages → animations → a11y → dark mode → responsive | +| `functions.php` | Theme setup, script enqueueing, Customizer registration, nav walker, helper functions | +| `front-page.php` | Home page: hero section → PRAutoBlogger widget → Peptide News feed | +| `header.php` | Global header with nav, dark mode toggle, mobile menu | +| `footer.php` | 4-column widget footer with copyright | +| `archive-peptide.php` | Peptide CPT archive grid | +| `single-peptide.php` | Single peptide detail page | +| `assets/js/navigation.js` | Mobile menu and nav functionality | +| `assets/js/theme.js` | Dark mode toggle, theme utilities | + +### Template parts + +- `template-parts/content.php` — Post/page content loop +- `template-parts/content-peptide.php` — Peptide card in archive +- `template-parts/content-none.php` — Empty state + +## Coding Conventions + +- **PHP**: WordPress coding standards — tabs for indentation, Yoda conditions, braces on same line +- **CSS**: No preprocessor. Use CSS custom properties (defined in `:root`). All theme classes prefixed `.ps-` +- **JS**: Vanilla ES6+, no build step. Files in `assets/js/` must pass `node --check` +- **No `!important`** on global elements — prevents plugin conflicts +- Plugin CSS namespaces are untouched (`.pn-*` for Peptide News, `.psa-*` for Peptide Search AI, `.prab-*` for PRAutoBlogger) + +## Design System + +- **Font**: Inter (Google Fonts) with system fallback +- **Scale**: 1.125x modular, 8px grid base +- **Primary color**: `#0066CC` (light) / `#3B82F6` (dark) +- **Breakpoints**: 480px, 768px, 1024px, 1440px (mobile-first) +- **Dark mode**: CSS custom properties swap via `html[data-theme="dark"]` or `prefers-color-scheme: dark` + +## Plugin Integration + +The theme integrates with three companion plugins via shortcodes, all wrapped in `shortcode_exists()` for graceful degradation: + +1. **Peptide Search AI** (`[peptide_search]`) — AI search in hero section +2. **Peptide News** (`[peptide_news]`) — News feed on front page +3. **PRAutoBlogger** (`[prautoblogger_posts]`) — Auto-generated research articles on front page + +## Customizer Options + +Editable via Appearance → Customize: +- Hero title, subtitle, search placeholder +- Footer copyright text +- Dark mode default on/off + +## Version + +Current: **v1.3.1** (defined in `style.css` header and `PEPTIDE_STARTER_VERSION` constant in `functions.php`) + +When bumping version, update both locations. diff --git a/CONVENTIONS.md b/CONVENTIONS.md new file mode 100644 index 0000000..76200bd --- /dev/null +++ b/CONVENTIONS.md @@ -0,0 +1,600 @@ +# Peptide Starter Theme - Conventions & Development Guide + +## Naming Conventions + +### PHP Functions + +All custom PHP functions are prefixed with `peptide_starter_`: + +```php +// Good +function peptide_starter_get_hero_title() { } +function peptide_starter_setup() { } +function peptide_starter_widgets_init() { } +function peptide_starter_pagination() { } + +// Bad +function get_hero_title() { } +function setup_theme() { } +``` + +**Pattern:** `peptide_starter_{verb}_{noun}` or `peptide_starter_get_{option}` + +**Common function types:** +- `peptide_starter_get_*()` — Retrieve theme mod or option +- `peptide_starter_{noun}_init()` — Initialization functions +- `peptide_starter_is_*()` — Boolean checks +- `peptide_starter_render_*()` — Output HTML + +### CSS Classes + +All theme CSS classes are prefixed with `.ps-`: + +```css +/* Components */ +.ps-btn /* Button component */ +.ps-btn-primary /* Button variant */ +.ps-btn-sm /* Button size */ +.ps-card /* Card component */ +.ps-badge /* Badge component */ +.ps-alert-info /* Alert variant */ + +/* Utilities */ +.ps-container /* Max-width wrapper */ +.ps-pagination /* Pagination nav */ +.ps-search-form /* Search form */ +.ps-search-input /* Search input */ + +/* Regions */ +.site-header /* No prefix; global region */ +.site-footer /* No prefix; global region */ +.site-main /* No prefix; global region */ +.hero-section /* Hero region specific */ +``` + +**Pattern:** `.ps-{component}` for components, `.ps-{component}-{variant}` for variants + +**Reserved prefixes (do not use):** +- `.pn-*` — Peptide News plugin +- `.psa-*` — Peptide Search AI plugin +- `.prab-*` — PRAutoBlogger plugin + +### CSS Variables (Custom Properties) + +All design tokens are defined in `style.css :root`: + +```css +/* Colors */ +--text-primary /* Main text color */ +--text-secondary /* Secondary text color */ +--bg-primary /* Main background */ +--bg-secondary /* Secondary background */ + +/* Spacing (8px grid base) */ +--spacing-xs: 4px /* 0.5 × base */ +--spacing-sm: 8px /* 1 × base */ +--spacing-md: 16px /* 2 × base */ +--spacing-lg: 24px /* 3 × base */ +--spacing-xl: 32px /* 4 × base */ + +/* Typography */ +--font-sans /* Primary font family */ +--font-mono /* Monospace font */ +--text-h1 /* H1 size */ +--text-h1-weight /* H1 weight */ +``` + +**Usage:** +```css +.ps-card { + padding: var(--spacing-md); + color: var(--text-primary); +} +``` + +### Template Part Naming + +Template parts in `template-parts/` follow WordPress convention: + +``` +template-parts/content.php /* Default loop item */ +template-parts/content-{post-type}.php /* CPT-specific */ +template-parts/content-none.php /* Empty state */ +template-parts/{section}-{name}.php /* Specific parts (if added) */ +``` + +**Calling template parts:** +```php +get_template_part( 'template-parts/content', get_post_type() ); +get_template_part( 'template-parts/content', 'none' ); +``` + +### Hook Names + +Custom hooks use the `peptide_starter_` prefix: + +```php +// Filters +apply_filters( 'peptide_starter_show_newsletter', true ); + +// Actions (if any added in future) +do_action( 'peptide_starter_hero_before' ); +``` + +### File Organization + +- **Root:** Theme core files (functions.php, header.php, footer.php, etc.) +- **assets/js/:** Client-side scripts only; no bundling +- **assets/css/:** (None; all CSS in style.css) +- **languages/:** Translation files +- **template-parts/:** Reusable template parts + +## Step-by-Step Guides + +### How to Add a New Template + +**Scenario:** Create a custom landing page template + +1. Create file: `landing-{name}.php` + ```php + + +
+ +
+ + Template dropdown + +4. Test the template: + - Create/edit a page and select the template + - Verify hero, dark mode toggle, footer all appear correctly + +**Best practices:** +- Always include `get_header()` and `get_footer()` +- Wrap main content in `
` +- Use `.ps-container` for consistent max-width +- Use existing component classes (`.ps-card`, `.ps-btn`, etc.) +- Keep templates under 150 lines; extract complex parts to `template-parts/` + +### How to Add a New Plugin Integration + +**Scenario:** Integrate a new plugin (e.g., "Research Database") via shortcode + +1. **In front-page.php**, add a conditional shortcode block: + ```php + +
+
+

+ +
+
+ + ``` + +2. **In functions.php**, optionally add inline overrides if plugin CSS needs adjustment: + ```php + function peptide_starter_plugin_overrides() { + wp_add_inline_style( 'peptide-starter-style', ' + /* Override plugin styles here */ + .rdb-card { /* example */ } + ' ); + } + add_action( 'wp_enqueue_scripts', 'peptide_starter_plugin_overrides', 99 ); + ``` + +3. **Document the integration** in CLAUDE.md: + - Plugin namespace (e.g., `.rdb-*`) + - Shortcode name and parameters + - Expected styling classes + +4. **If plugin needs dark mode coordination:** + - Ask plugin author to listen to `themechange` event (see ARCHITECTURE.md) + - Or have plugin read `html[data-theme]` attribute + - Provide plugin with CSS custom properties: `getComputedStyle(document.documentElement).getPropertyValue('--text-primary')` + +5. **Test:** + - Activate plugin and visit front page + - Verify shortcode renders + - Toggle dark mode; verify plugin responds + - Deactivate plugin; verify page still renders (fallback gracefully) + +**Important:** Always wrap shortcodes in `shortcode_exists()` checks so theme doesn't break if plugin is missing. + +### How to Add a New CSS Component + +**Scenario:** Create a new "pill" button style (small, rounded, inline) + +1. **In style.css**, find the Components section (around line 500) + +2. **Add new component class:** + ```css + /* Pill buttons */ + .ps-btn-pill { + display: inline-block; + padding: var(--spacing-xs) var(--spacing-sm); + border-radius: 9999px; + font-size: 12px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + } + + .ps-btn-pill.ps-btn-primary { + background-color: var(--color-primary); + color: white; + } + + .ps-btn-pill.ps-btn-secondary { + background-color: transparent; + border: 1px solid var(--color-primary); + color: var(--color-primary); + } + ``` + +3. **Add dark mode support** (in Dark Mode section, around line 1200): + ```css + html[data-theme="dark"] .ps-btn-pill.ps-btn-secondary { + border-color: var(--color-primary-dark); + color: var(--color-primary-dark); + } + ``` + +4. **Add responsive sizes if needed** (in Responsive section, around line 1400): + ```css + @media (max-width: 767px) { + .ps-btn-pill { + padding: var(--spacing-xs) var(--spacing-xs); + } + } + ``` + +5. **Usage in templates:** + ```html + Beta + ``` + +6. **Test:** + - Light mode: verify colors, sizing, alignment + - Dark mode: toggle and verify contrast + - Mobile (480px): verify responsive adjustments + - Keyboard: focus state should be visible + +**Best practices:** +- Use CSS variables (`var(--spacing-*)`, `var(--color-*)`) not hardcoded values +- Avoid `!important` unless absolutely necessary +- Include dark mode support immediately (no `.ps-*` is light-only) +- Group related rules with comments +- Keep component definitions self-contained (<100 lines) + +### How to Add a New Customizer Setting + +**Scenario:** Add a new "Primary Accent Color" customizer option + +1. **In functions.php** (around line 143), add a new section: + ```php + $wp_customize->add_section( 'peptide_starter_colors', array( + 'title' => esc_html__( 'Colors', 'peptide-starter' ), + 'priority' => 45, + ) ); + ``` + +2. **Add a setting:** + ```php + $wp_customize->add_setting( 'primary_accent_color', array( + 'default' => '#0066CC', + 'transport' => 'postMessage', + 'sanitize_callback' => 'sanitize_hex_color', + ) ); + ``` + +3. **Add a control:** + ```php + $wp_customize->add_control( new WP_Customize_Color_Control( + $wp_customize, + 'primary_accent_color', + array( + 'label' => esc_html__( 'Primary Accent Color', 'peptide-starter' ), + 'section' => 'peptide_starter_colors', + 'settings' => 'primary_accent_color', + ) + ) ); + ``` + +4. **Create a getter function:** + ```php + function peptide_starter_get_primary_accent_color() { + return get_theme_mod( 'primary_accent_color', '#0066CC' ); + } + ``` + +5. **Output in front-end** (e.g., in footer.php): + ```php + + ``` + + Or better, use inline style in `wp_head`: + ```php + function peptide_starter_custom_colors() { + $accent = peptide_starter_get_primary_accent_color(); + ?> + + 'postMessage'` — Instant preview (needs JS listener) +- `'transport' => 'refresh'` — Full page refresh + +For color/text changes, use `postMessage` with a JS listener in theme.js: +```javascript +wp_customize.bind( 'setting:primary_accent_color', function( setting ) { + setting.bind( function( newval ) { + document.documentElement.style.setProperty('--color-accent', newval); + }); +}); +``` + +## Error Handling Patterns + +### Theme Doesn't Break If Plugin Missing + +**Pattern: Graceful Degradation** +```php +// Good — shortcode_exists check +if ( shortcode_exists( 'peptide_search' ) ) { + echo do_shortcode( '[peptide_search]' ); +} else { + get_search_form(); // Fallback to WordPress default +} + +// Bad — assumes plugin exists +echo do_shortcode( '[peptide_search]' ); // Renders [peptide_search] as text if missing +``` + +### Menu Fallback + +**In header.php:** +```php +wp_nav_menu( array( + 'theme_location' => 'primary', + 'fallback_cb' => 'peptide_starter_primary_menu_fallback', // Function defined in header + 'walker' => new Peptide_Starter_Nav_Walker(), +) ); + +function peptide_starter_primary_menu_fallback() { + // Renders Home, About, Peptides links if no menu assigned +} +``` + +### Widget Area Check + +**In footer.php:** +```php +if ( is_active_sidebar( 'footer-1' ) ) { + dynamic_sidebar( 'footer-1' ); +} else { + // Render default content + echo '

' . esc_html__( 'About', 'peptide-starter' ) . '

'; +} +``` + +### Class Existence Check + +**For WooCommerce:** +```php +if ( class_exists( 'WooCommerce' ) ) { + // Render cart icon +} +``` + +**For post types:** +```php +if ( post_type_exists( 'peptide' ) ) { + echo '' . __( 'Peptides' ) . ''; +} +``` + +### Input Validation + +All user-facing output must be escaped: + +```php +// Good +echo esc_html( get_the_title() ); +echo esc_url( home_url( '/' ) ); +echo esc_attr( $html_attribute_value ); + +// Bad +echo get_the_title(); +echo home_url( '/' ); +``` + +## Dark Mode Implementation Guide + +### How Dark Mode Works in Peptide Starter + +1. **Inline script in `wp_head`** (functions.php, line 406-422) + - Runs immediately before CSS loads + - Prevents white flash by setting `html[data-theme]` early + - Checks in order: localStorage → system pref → customizer default + +2. **CSS selectors** (style.css, line ~1200 "Dark Mode") + - Light mode: default colors in `:root` + - Dark mode: `html[data-theme="dark"]` selector overrides + +3. **JavaScript listener** (theme.js, line 47-63) + - Responds to dark mode toggle button + - Updates localStorage + - Dispatches `themechange` event to plugins + - Updates sun/moon icon visibility + +### Adding Dark Mode to a New Component + +**Example: New Alert Style** + +1. **Define light mode in CSS:** + ```css + .ps-alert-custom { + padding: var(--spacing-md); + background-color: #E3F2FD; /* Light blue */ + color: #0D47A1; /* Dark blue text */ + border: 1px solid #90CAF9; + } + ``` + +2. **Add dark mode override:** + ```css + html[data-theme="dark"] .ps-alert-custom { + background-color: #1A237E; /* Dark blue bg */ + color: #E3F2FD; /* Light text */ + border-color: #3F51B5; /* Darker border */ + } + ``` + +3. **Test:** + - Load page in light mode → verify colors + - Toggle dark mode → verify override colors are applied + - Open DevTools → verify `html[data-theme="dark"]` attribute set + - Check localStorage `peptide-starter-theme` is updated + +### Helping Plugins Support Dark Mode + +**If a plugin doesn't listen to dark mode changes:** + +1. Ask plugin author to listen to the `themechange` event: + ```javascript + document.addEventListener('themechange', (e) => { + const theme = e.detail.theme; + // Update plugin styles based on theme + }); + ``` + +2. Or use a MutationObserver to watch for `data-theme` changes: + ```javascript + const observer = new MutationObserver(() => { + const theme = document.documentElement.getAttribute('data-theme'); + // Update styles + }); + observer.observe(document.documentElement, { attributes: true }); + ``` + +3. Or read CSS variables in the plugin stylesheet: + ```css + .plugin-card { + color: var(--text-primary); + background-color: var(--bg-secondary); + } + ``` + +### CSS Variables for Dark Mode + +All available variables (defined in `:root`): + +```css +--text-primary /* Main text (light: #111827, dark: #F9FAFB) */ +--text-secondary /* Secondary text (light: #6B7280, dark: #D1D5DB) */ +--bg-primary /* Main background (light: #FFFFFF, dark: #111827) */ +--bg-secondary /* Secondary bg (light: #F3F4F6, dark: #1F2937) */ +--color-primary /* Primary accent (light: #0066CC, dark: #3B82F6) */ +--border-color /* Border color (light: #E5E7EB, dark: #374151) */ +``` + +## Testing Checklist + +When modifying the theme, verify: + +- [ ] **Light mode** — All text readable, colors correct +- [ ] **Dark mode** — Toggle to dark, verify colors swap, no white flash on reload +- [ ] **Mobile** (480px) — Navigation collapses, search/dark mode icons present +- [ ] **Tablet** (768px) — Layout adapts, footer 2-column if needed +- [ ] **Desktop** (1024px+) — Full layout renders correctly +- [ ] **Plugins active** — Shortcodes render, no JS errors in console +- [ ] **Plugins inactive** — Graceful fallbacks work (search form, no broken layout) +- [ ] **Accessibility** — Tab through page, all interactive elements reachable +- [ ] **Focus states** — Every button/link has visible focus outline +- [ ] **Keyboard navigation** — Escape closes overlays, arrow keys in menu if relevant + +## Common Tasks & Commands + +**Check JavaScript syntax:** +```bash +node --check assets/js/theme.js +node --check assets/js/navigation.js +``` + +**View theme version:** +```php +echo PEPTIDE_STARTER_VERSION; // Currently: 1.3.1 +``` + +**Update theme version (when releasing):** +1. Edit `style.css` line 7: `Version: X.Y.Z` +2. Edit `functions.php` line 14: `define( 'PEPTIDE_STARTER_VERSION', 'X.Y.Z' )` + +**Deploy to production:** +- Push to `main` branch on GitHub +- GitHub Actions runs rsync to Hostinger via SSH +- LiteSpeed cache clears automatically + +**Debug active plugins:** +```php +// In functions.php or template +$active = get_option( 'active_plugins' ); +error_log( print_r( $active, true ) ); +``` + +**View Customizer settings:** +```php +echo get_theme_mod( 'hero_title' ); +echo get_theme_mod( 'dark_mode_default', false ); +``` + +## Related Documentation + +- **ARCHITECTURE.md** — System design, data flows, integrations +- **CLAUDE.md** — Developer context and quick reference +- **README.md** — User-facing feature documentation +- **CHANGELOG.md** — Version history diff --git a/README.md b/README.md index ffe5238..f842578 100644 --- a/README.md +++ b/README.md @@ -1,267 +1,267 @@ -# Peptide Starter Theme - -A premium, accessible WordPress theme for the Peptide Repo scientific peptide reference database. - -## Features - -- **Modern Design** - Clean, minimalist aesthetic inspired by Apple's human interface guidelines -- **Dark Mode Support** - Full dark mode via `prefers-color-scheme` and manual toggle -- **Accessibility** - WCAG AA compliant with proper focus indicators and semantic HTML -- **Plugin Harmony** - Respects plugin CSS namespaces (`.pn-*` for Peptide News plugin) -- **Responsive Design** - Mobile-first approach with proper breakpoints (480px, 768px, 1024px, 1440px) -- **Typography** - Inter font with 1.125x modular scale for excellent readability -- **Component Library** - Pre-built buttons, cards, forms, badges, alerts, and pagination -- **Customizer Support** - Theme options for hero title, subtitle, search placeholder, footer text -- **Performance** - Lightweight CSS architecture using custom properties instead of !important - -## Installation - -1. Download `peptide-starter.zip` -2. Go to WordPress Admin → Appearance → Themes -3. Click "Add New" → "Upload Theme" -4. Select the zip file and click "Install Now" -5. Click "Activate" to use the theme - -## Theme Structure - -``` -peptide-starter/ -├── style.css # Main stylesheet with all CSS -├── functions.php # Theme setup and functionality -├── header.php # Global header with navigation -├── footer.php # Global footer with widgets -├── index.php # Fallback template -├── front-page.php # Home page with hero + news feed -├── page.php # Generic page template -├── archive-peptide.php # Peptide archive/database view -├── single-peptide.php # Single peptide detail page -├── 404.php # 404 error page -├── searchform.php # Search form template -├── screenshot.png # Theme screenshot for WordPress -├── assets/ -│ └── js/ -│ ├── navigation.js # Mobile menu and nav functionality -│ └── theme.js # Dark mode toggle and utilities -├── template-parts/ -│ ├── content.php # Post/page content template -│ └── content-none.php # Empty state template -└── languages/ # Translation files -``` - -## Design System - -### Color Palette - -**Light Mode:** -- Primary: `#0066CC` (Blue) -- Text Primary: `#111827` (Near Black) -- Text Secondary: `#6B7280` (Gray) -- Background: `#FFFFFF` (White) - -**Dark Mode:** -- Primary: `#3B82F6` (Bright Blue) -- Text Primary: `#F9FAFB` (Near White) -- Text Secondary: `#D1D5DB` (Light Gray) -- Background: `#111827` (Near Black) - -### Typography - -All sizes use a **1.125x modular scale** with **8px grid base**: - -- **H1**: 48px, 700 weight -- **H2**: 40px, 700 weight -- **H3**: 32px, 600 weight -- **H4**: 24px, 600 weight -- **H5**: 20px, 700 weight (fixed from 18px per Design Director) -- **H6**: 16px, 500 weight -- **Body**: 14px, 400 weight (fixed from 16px per Design Director) - -Font family: Inter (with system font fallback) - -### Spacing - -8px base unit grid: -- `--spacing-xs`: 4px -- `--spacing-sm`: 8px -- `--spacing-md`: 16px -- `--spacing-lg`: 24px -- `--spacing-xl`: 32px -- `--spacing-2xl`: 48px -- `--spacing-3xl`: 64px -- `--spacing-4xl`: 80px -- `--spacing-5xl`: 96px - -### Components - -**Buttons:** -- `.ps-btn` - Base button -- `.ps-btn-primary` - Primary action (blue) -- `.ps-btn-secondary` - Secondary action (outlined) -- `.ps-btn-tertiary` - Tertiary action (transparent) -- `.ps-btn-sm`, `.ps-btn-lg`, `.ps-btn-full` - Size variants - -**Cards:** -- `.ps-card` - Standard card with border and hover effect - -**Forms:** -- Proper focus states with 15% opacity focus ring -- Error states with red border and light red background -- Support for text, email, password, search, number, url inputs - -**Alerts:** -- `.ps-alert-info` - Blue alert -- `.ps-alert-success` - Green alert -- `.ps-alert-warning` - Amber alert -- `.ps-alert-error` - Red alert - -**Badges & Tags:** -- `.ps-badge` - Static badge -- `.ps-tag` - Dismissible tag - -## Dark Mode - -### How it Works - -The theme detects dark mode in this order: -1. User's explicit choice via toggle (stored in `localStorage`) -2. System preference via `prefers-color-scheme: dark` -3. Default to light mode - -The theme applies dark colors via: -- `@media (prefers-color-scheme: dark)` - System preference -- `html[data-theme="dark"]` - User override (takes precedence) - -### Plugin Integration - -The theme broadcasts dark mode changes to plugins via: - -1. **Custom Event**: `themechange` event on `document` - ```javascript - document.addEventListener('themechange', (e) => { - console.log('Theme is now:', e.detail.theme); - }); - ``` - -2. **Data Attribute**: `data-theme` on `` - ```javascript - const theme = document.documentElement.getAttribute('data-theme'); - ``` - -3. **CSS Custom Properties**: All colors controlled via CSS variables - -## Plugin Compatibility - -### Peptide News Plugin -The theme provides optimal styling for the Peptide News plugin: -- News feed grid adapts to screen size (1-4 columns) -- Plugin `.pn-*` classes are untouched by theme -- Plugin handles its own card styling -- Dark mode colors available via CSS custom properties - -### WooCommerce -- Cart icon in header (if WooCommerce is active) -- Lightweight integration without full theme styling - -## Customizer Options - -Go to **Appearance → Customize** to modify: -- **Hero Title** - Main headline on home page -- **Hero Subtitle** - Subheading under title -- **Search Placeholder** - Text in search input -- **Footer Copyright** - Copyright/attribution text -- **Dark Mode Default** - Enable dark mode on first visit - -## Accessibility - -The theme meets **WCAG AA standards**: -- All color combinations have 4.5:1+ contrast ratio -- Proper heading hierarchy (H1-H6) -- Focus indicators on all interactive elements (2px outline) -- Keyboard navigation support -- Skip-to-main-content link -- Semantic HTML with proper landmarks -- Form labels properly associated with inputs -- Alt text for images - -## Responsive Breakpoints - -- **Mobile**: 320px - 479px -- **Tablet**: 480px - 767px -- **Desktop**: 768px - 1023px -- **Large**: 1024px - 1439px -- **Extra Large**: 1440px+ - -The theme uses **mobile-first CSS** - styles start mobile and add complexity at larger breakpoints. - -## Performance - -- **No `!important` on global elements** - Prevents plugin conflicts -- **CSS Custom Properties** - Single source of truth for design tokens -- **Minimal JavaScript** - Only essential interactivity -- **Font loading** - Inter from Google Fonts with `display=swap` -- **Proper scoping** - Theme classes prefixed with `.ps-` to avoid conflicts - -## Development - -### Adding Custom Styles - -Modify `style.css` sections: -1. Components - Add new component classes -2. Pages - Add page-specific styles -3. Responsive - Add mobile/tablet/desktop rules - -### CSS Architecture - -The stylesheet is organized in this order: -1. Reset -2. CSS Custom Properties (variables) -3. Typography -4. Global Styles -5. Components -6. Regions (header, footer) -7. Pages -8. Animations -9. Accessibility -10. Dark Mode -11. Responsive - -### Adding New Templates - -Create new template files following WordPress standards: -- `page-{slug}.php` - Specific page templates -- `archive-{post-type}.php` - Archive templates -- `single-{post-type}.php` - Single post templates -- `template-parts/{name}.php` - Reusable template parts - -## Browser Support - -- Chrome/Edge (latest 2 versions) -- Firefox (latest 2 versions) -- Safari (latest 2 versions) -- iOS Safari 12+ -- Chrome Android - -## License - -GPL v2 or later - See LICENSE file - -## Credits - -Designed with principles from: -- Apple Human Interface Guidelines (2025) -- Modern Scientific Databases (PubMed, Nature) -- Premium SaaS Design (Linear, Stripe, Vercel) -- WordPress Best Practices - -## Support - -For issues and feature requests, visit: https://github.com/peptiderepo/peptide-starter - -## Changelog - -### Version 1.0.0 (Initial Release) -- Initial theme launch -- All core features implemented -- Full dark mode support -- Plugin harmony architecture -- Accessibility compliance (WCAG AA) +# Peptide Starter Theme + +A premium, accessible WordPress theme for the Peptide Repo scientific peptide reference database. + +## Features + +- **Modern Design** - Clean, minimalist aesthetic inspired by Apple's human interface guidelines +- **Dark Mode Support** - Full dark mode via `prefers-color-scheme` and manual toggle +- **Accessibility** - WCAG AA compliant with proper focus indicators and semantic HTML +- **Plugin Harmony** - Respects plugin CSS namespaces (`.pn-*` for Peptide News plugin) +- **Responsive Design** - Mobile-first approach with proper breakpoints (480px, 768px, 1024px, 1440px) +- **Typography** - Inter font with 1.125x modular scale for excellent readability +- **Component Library** - Pre-built buttons, cards, forms, badges, alerts, and pagination +- **Customizer Support** - Theme options for hero title, subtitle, search placeholder, footer text +- **Performance** - Lightweight CSS architecture using custom properties instead of !important + +## Installation + +1. Download `peptide-starter.zip` +2. Go to WordPress Admin → Appearance → Themes +3. Click "Add New" → "Upload Theme" +4. Select the zip file and click "Install Now" +5. Click "Activate" to use the theme + +## Theme Structure + +``` +peptide-starter/ +├── style.css # Main stylesheet with all CSS +├── functions.php # Theme setup and functionality +├── header.php # Global header with navigation +├── footer.php # Global footer with widgets +├── index.php # Fallback template +├── front-page.php # Home page with hero + news feed +├── page.php # Generic page template +├── archive-peptide.php # Peptide archive/database view +├── single-peptide.php # Single peptide detail page +├── 404.php # 404 error page +├── searchform.php # Search form template +├── screenshot.png # Theme screenshot for WordPress +├── assets/ +│ └── js/ +│ ├── navigation.js # Mobile menu and nav functionality +│ └── theme.js # Dark mode toggle and utilities +├── template-parts/ +│ ├── content.php # Post/page content template +│ └── content-none.php # Empty state template +└── languages/ # Translation files +``` + +## Design System + +### Color Palette + +**Light Mode:** +- Primary: `#0066CC` (Blue) +- Text Primary: `#111827` (Near Black) +- Text Secondary: `#6B7280` (Gray) +- Background: `#FFFFFF` (White) + +**Dark Mode:** +- Primary: `#3B82F6` (Bright Blue) +- Text Primary: `#F9FAFB` (Near White) +- Text Secondary: `#D1D5DB` (Light Gray) +- Background: `#111827` (Near Black) + +### Typography + +All sizes use a **1.125x modular scale** with **8px grid base**: + +- **H1**: 48px, 700 weight +- **H2**: 40px, 700 weight +- **H3**: 32px, 600 weight +- **H4**: 24px, 600 weight +- **H5**: 20px, 700 weight (fixed from 18px per Design Director) +- **H6**: 16px, 500 weight +- **Body**: 14px, 400 weight (fixed from 16px per Design Director) + +Font family: Inter (with system font fallback) + +### Spacing + +8px base unit grid: +- `--spacing-xs`: 4px +- `--spacing-sm`: 8px +- `--spacing-md`: 16px +- `--spacing-lg`: 24px +- `--spacing-xl`: 32px +- `--spacing-2xl`: 48px +- `--spacing-3xl`: 64px +- `--spacing-4xl`: 80px +- `--spacing-5xl`: 96px + +### Components + +**Buttons:** +- `.ps-btn` - Base button +- `.ps-btn-primary` - Primary action (blue) +- `.ps-btn-secondary` - Secondary action (outlined) +- `.ps-btn-tertiary` - Tertiary action (transparent) +- `.ps-btn-sm`, `.ps-btn-lg`, `.ps-btn-full` - Size variants + +**Cards:** +- `.ps-card` - Standard card with border and hover effect + +**Forms:** +- Proper focus states with 15% opacity focus ring +- Error states with red border and light red background +- Support for text, email, password, search, number, url inputs + +**Alerts:** +- `.ps-alert-info` - Blue alert +- `.ps-alert-success` - Green alert +- `.ps-alert-warning` - Amber alert +- `.ps-alert-error` - Red alert + +**Badges & Tags:** +- `.ps-badge` - Static badge +- `.ps-tag` - Dismissible tag + +## Dark Mode + +### How it Works + +The theme detects dark mode in this order: +1. User's explicit choice via toggle (stored in `localStorage`) +2. System preference via `prefers-color-scheme: dark` +3. Default to light mode + +The theme applies dark colors via: +- `@media (prefers-color-scheme: dark)` - System preference +- `html[data-theme="dark"]` - User override (takes precedence) + +### Plugin Integration + +The theme broadcasts dark mode changes to plugins via: + +1. **Custom Event**: `themechange` event on `document` + ```javascript + document.addEventListener('themechange', (e) => { + console.log('Theme is now:', e.detail.theme); + }); + ``` + +2. **Data Attribute**: `data-theme` on `` + ```javascript + const theme = document.documentElement.getAttribute('data-theme'); + ``` + +3. **CSS Custom Properties**: All colors controlled via CSS variables + +## Plugin Compatibility + +### Peptide News Plugin +The theme provides optimal styling for the Peptide News plugin: +- News feed grid adapts to screen size (1-4 columns) +- Plugin `.pn-*` classes are untouched by theme +- Plugin handles its own card styling +- Dark mode colors available via CSS custom properties + +### WooCommerce +- Cart icon in header (if WooCommerce is active) +- Lightweight integration without full theme styling + +## Customizer Options + +Go to **Appearance → Customize** to modify: +- **Hero Title** - Main headline on home page +- **Hero Subtitle** - Subheading under title +- **Search Placeholder** - Text in search input +- **Footer Copyright** - Copyright/attribution text +- **Dark Mode Default** - Enable dark mode on first visit + +## Accessibility + +The theme meets **WCAG AA standards**: +- All color combinations have 4.5:1+ contrast ratio +- Proper heading hierarchy (H1-H6) +- Focus indicators on all interactive elements (2px outline) +- Keyboard navigation support +- Skip-to-main-content link +- Semantic HTML with proper landmarks +- Form labels properly associated with inputs +- Alt text for images + +## Responsive Breakpoints + +- **Mobile**: 320px - 479px +- **Tablet**: 480px - 767px +- **Desktop**: 768px - 1023px +- **Large**: 1024px - 1439px +- **Extra Large**: 1440px+ + +The theme uses **mobile-first CSS** - styles start mobile and add complexity at larger breakpoints. + +## Performance + +- **No `!important` on global elements** - Prevents plugin conflicts +- **CSS Custom Properties** - Single source of truth for design tokens +- **Minimal JavaScript** - Only essential interactivity +- **Font loading** - Inter from Google Fonts with `display=swap` +- **Proper scoping** - Theme classes prefixed with `.ps-` to avoid conflicts + +## Development + +### Adding Custom Styles + +Modify `style.css` sections: +1. Components - Add new component classes +2. Pages - Add page-specific styles +3. Responsive - Add mobile/tablet/desktop rules + +### CSS Architecture + +The stylesheet is organized in this order: +1. Reset +2. CSS Custom Properties (variables) +3. Typography +4. Global Styles +5. Components +6. Regions (header, footer) +7. Pages +8. Animations +9. Accessibility +10. Dark Mode +11. Responsive + +### Adding New Templates + +Create new template files following WordPress standards: +- `page-{slug}.php` - Specific page templates +- `archive-{post-type}.php` - Archive templates +- `single-{post-type}.php` - Single post templates +- `template-parts/{name}.php` - Reusable template parts + +## Browser Support + +- Chrome/Edge (latest 2 versions) +- Firefox (latest 2 versions) +- Safari (latest 2 versions) +- iOS Safari 12+ +- Chrome Android + +## License + +GPL v2 or later - See LICENSE file + +## Credits + +Designed with principles from: +- Apple Human Interface Guidelines (2025) +- Modern Scientific Databases (PubMed, Nature) +- Premium SaaS Design (Linear, Stripe, Vercel) +- WordPress Best Practices + +## Support + +For issues and feature requests, visit: https://github.com/peptiderepo/peptide-starter + +## Changelog + +### Version 1.0.0 (Initial Release) +- Initial theme launch +- All core features implemented +- Full dark mode support +- Plugin harmony architecture +- Accessibility compliance (WCAG AA) diff --git a/assets/js/auth.js b/assets/js/auth.js new file mode 100644 index 0000000..b393e93 --- /dev/null +++ b/assets/js/auth.js @@ -0,0 +1,192 @@ +/** + * Authentication Page — Form Toggle & Validation + * Peptide Starter Theme + * + * Handles: + * - Toggle between sign-in and register forms + * - Client-side form validation + * - Password strength indicator + * - AJAX form submission to avoid full page reload + * + * @see page-auth.php — renders the auth forms + * @see functions.php — AJAX handlers (ps_auth_login, ps_auth_register) + * + * Conditionally enqueued only on pages using the Sign In / Register template. + */ + +(function() { + 'use strict'; + + var loginWrap = document.getElementById('ps-auth-login'); + var registerWrap = document.getElementById('ps-auth-register'); + var showRegister = document.getElementById('ps-show-register'); + var showLogin = document.getElementById('ps-show-login'); + var messages = document.getElementById('ps-auth-messages'); + var loginForm = document.getElementById('ps-login-form'); + var registerForm = document.getElementById('ps-register-form'); + + if (!loginWrap || !registerWrap) { + return; + } + + /** + * Toggle between login and register views. + * @param {string} view - 'login' or 'register'. + */ + function showView(view) { + clearMessages(); + if (view === 'register') { + loginWrap.classList.add('ps-auth-form-wrap--hidden'); + registerWrap.classList.remove('ps-auth-form-wrap--hidden'); + } else { + registerWrap.classList.add('ps-auth-form-wrap--hidden'); + loginWrap.classList.remove('ps-auth-form-wrap--hidden'); + } + } + + if (showRegister) { + showRegister.addEventListener('click', function(e) { + e.preventDefault(); + showView('register'); + }); + } + + if (showLogin) { + showLogin.addEventListener('click', function(e) { + e.preventDefault(); + showView('login'); + }); + } + + // Check URL hash on load for deep linking. + if (window.location.hash === '#register') { + showView('register'); + } + + /** + * Display a message in the auth messages area. + * @param {string} text - Message text. + * @param {string} type - 'success', 'error', or 'info'. + */ + function showMessage(text, type) { + if (!messages) { + return; + } + var alertClass = 'ps-alert ps-alert-' + (type || 'info'); + messages.innerHTML = '

' + escapeHtml(text) + '

'; + } + + function clearMessages() { + if (messages) { + messages.innerHTML = ''; + } + } + + /** + * Escape HTML entities to prevent XSS in message display. + * @param {string} str - Raw string. + * @returns {string} Escaped string. + */ + function escapeHtml(str) { + var div = document.createElement('div'); + div.textContent = str; + return div.innerHTML; + } + + /** + * Password strength indicator. + * Updates the #ps-password-strength element with a rating. + */ + var passwordInput = document.getElementById('ps-reg-password'); + var strengthEl = document.getElementById('ps-password-strength'); + + if (passwordInput && strengthEl) { + passwordInput.addEventListener('input', function() { + var val = passwordInput.value; + var score = 0; + var label = ''; + + if (val.length >= 8) { score++; } + if (val.length >= 12) { score++; } + if (/[a-z]/.test(val) && /[A-Z]/.test(val)) { score++; } + if (/\d/.test(val)) { score++; } + if (/[^a-zA-Z0-9]/.test(val)) { score++; } + + if (val.length === 0) { + label = ''; + } else if (score <= 1) { + label = 'Weak'; + } else if (score <= 3) { + label = 'Fair'; + } else { + label = 'Strong'; + } + + strengthEl.innerHTML = label; + }); + } + + /** + * AJAX form submission handler. + * @param {HTMLFormElement} form - The form to submit. + */ + function submitForm(form) { + var formData = new FormData(form); + var btn = form.querySelector('button[type="submit"]'); + var origText = btn.textContent; + + btn.disabled = true; + btn.textContent = '...'; + clearMessages(); + + var xhr = new XMLHttpRequest(); + xhr.open('POST', (typeof peptideStarterData !== 'undefined' ? peptideStarterData.ajaxUrl : '/wp-admin/admin-ajax.php')); + xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + + xhr.onload = function() { + btn.disabled = false; + btn.textContent = origText; + + try { + var resp = JSON.parse(xhr.responseText); + if (resp.success && resp.data && resp.data.redirect) { + window.location.href = resp.data.redirect; + } else { + showMessage(resp.data && resp.data.message ? resp.data.message : 'An error occurred.', 'error'); + } + } catch (e) { + showMessage('An unexpected error occurred. Please try again.', 'error'); + } + }; + + xhr.onerror = function() { + btn.disabled = false; + btn.textContent = origText; + showMessage('Network error. Please check your connection.', 'error'); + }; + + xhr.send(formData); + } + + if (loginForm) { + loginForm.addEventListener('submit', function(e) { + e.preventDefault(); + if (!loginForm.checkValidity()) { + loginForm.reportValidity(); + return; + } + submitForm(loginForm); + }); + } + + if (registerForm) { + registerForm.addEventListener('submit', function(e) { + e.preventDefault(); + if (!registerForm.checkValidity()) { + registerForm.reportValidity(); + return; + } + submitForm(registerForm); + }); + } +})(); diff --git a/assets/js/documentation.js b/assets/js/documentation.js new file mode 100644 index 0000000..d866fd1 --- /dev/null +++ b/assets/js/documentation.js @@ -0,0 +1,145 @@ +/** + * Documentation Page — Table of Contents & Scroll Spy + * Peptide Starter Theme + * + * Handles: + * - Auto-generates ToC from h2 headings in the documentation body + * - Scroll spy highlights the active section in the sidebar + * - Smooth scrolling on ToC link click + * - Collapses sidebar to dropdown on mobile + * + * @see page-documentation.php — renders the two-column layout + * @see style.css — .ps-docs-* classes + * + * Conditionally enqueued only on pages using the Documentation template. + */ + +(function() { + 'use strict'; + + const docsBody = document.getElementById('ps-docs-body'); + const tocList = document.getElementById('ps-docs-toc-list'); + const sidebar = document.querySelector('.ps-docs-sidebar'); + + if (!docsBody || !tocList) { + return; + } + + const headings = docsBody.querySelectorAll('h2'); + + if (headings.length === 0) { + // Hide the sidebar if there are no headings to navigate. + if (sidebar) { + sidebar.style.display = 'none'; + } + return; + } + + /** + * Build the Table of Contents from h2 headings. + * Assigns IDs to headings that lack one for anchor linking. + */ + function buildToc() { + headings.forEach(function(heading, index) { + // Ensure every heading has an ID for anchor links. + if (!heading.id) { + heading.id = 'section-' + (index + 1); + } + + var li = document.createElement('li'); + var link = document.createElement('a'); + + link.href = '#' + heading.id; + link.textContent = heading.textContent; + link.className = 'ps-docs-toc-link'; + + link.addEventListener('click', function(e) { + e.preventDefault(); + smoothScrollTo(heading); + }); + + li.appendChild(link); + tocList.appendChild(li); + }); + } + + /** + * Smooth scroll to a target element, accounting for the sticky header. + * @param {HTMLElement} target The element to scroll to. + */ + function smoothScrollTo(target) { + var headerHeight = document.querySelector('.site-header') + ? document.querySelector('.site-header').offsetHeight + : 0; + var offset = 24; // Extra breathing room below header. + var top = target.getBoundingClientRect().top + window.pageYOffset - headerHeight - offset; + + window.scrollTo({ + top: top, + behavior: 'smooth' + }); + + // Update URL hash without jumping. + history.replaceState(null, '', '#' + target.id); + } + + /** + * Scroll spy — highlights the ToC link corresponding to the + * currently visible section. Uses IntersectionObserver for performance. + */ + function initScrollSpy() { + var tocLinks = tocList.querySelectorAll('.ps-docs-toc-link'); + + if (tocLinks.length === 0) { + return; + } + + var headerHeight = document.querySelector('.site-header') + ? document.querySelector('.site-header').offsetHeight + : 0; + + var observer = new IntersectionObserver(function(entries) { + entries.forEach(function(entry) { + if (entry.isIntersecting) { + var id = entry.target.id; + tocLinks.forEach(function(link) { + link.classList.remove('ps-docs-toc-link--active'); + if (link.getAttribute('href') === '#' + id) { + link.classList.add('ps-docs-toc-link--active'); + } + }); + } + }); + }, { + rootMargin: '-' + (headerHeight + 32) + 'px 0px -60% 0px', + threshold: 0 + }); + + headings.forEach(function(heading) { + observer.observe(heading); + }); + } + + /** + * Mobile dropdown toggle for the sidebar ToC. + */ + function initMobileToggle() { + var tocTitle = document.querySelector('.ps-docs-toc-title'); + + if (!tocTitle) { + return; + } + + tocTitle.addEventListener('click', function() { + // Only toggle on mobile (sidebar is not sticky). + if (window.innerWidth < 1024) { + sidebar.classList.toggle('ps-docs-sidebar--open'); + } + }); + } + + // Initialize. + buildToc(); + initScrollSpy(); + initMobileToggle(); +})(); diff --git a/assets/js/settings-panel.js b/assets/js/settings-panel.js new file mode 100644 index 0000000..a797d7d --- /dev/null +++ b/assets/js/settings-panel.js @@ -0,0 +1,163 @@ +/** + * Settings Panel — Open/Close & Contact Form + * Peptide Starter Theme + * + * Handles: + * - Toggle settings slide-out panel visibility + * - AJAX submission of the contact form + * - Overlay backdrop click to close + * - Escape key to close + * - Focus trap inside the panel for accessibility + * + * @see template-parts/settings-panel.php — panel HTML + * @see header.php — settings icon trigger button + * @see functions.php — AJAX handler for contact form + * + * Enqueued globally (lightweight — all listeners are passive until panel opens). + */ + +(function() { + 'use strict'; + + var panel = document.getElementById('ps-settings-panel'); + var overlay = document.getElementById('ps-settings-overlay'); + var closeBtn = document.getElementById('ps-settings-close'); + var openBtn = document.getElementById('ps-settings-toggle'); + var contactForm = document.getElementById('ps-contact-form'); + var statusEl = document.getElementById('ps-contact-status'); + + if (!panel || !overlay) { + return; + } + + /** + * Open the settings panel. + */ + function openPanel() { + panel.classList.add('ps-settings-panel--open'); + panel.setAttribute('aria-hidden', 'false'); + overlay.classList.add('ps-settings-overlay--active'); + overlay.setAttribute('aria-hidden', 'false'); + document.body.style.overflow = 'hidden'; + + // Focus the close button for keyboard users. + if (closeBtn) { + setTimeout(function() { closeBtn.focus(); }, 100); + } + } + + /** + * Close the settings panel. + */ + function closePanel() { + panel.classList.remove('ps-settings-panel--open'); + panel.setAttribute('aria-hidden', 'true'); + overlay.classList.remove('ps-settings-overlay--active'); + overlay.setAttribute('aria-hidden', 'true'); + document.body.style.overflow = ''; + + // Return focus to the trigger button. + if (openBtn) { + openBtn.focus(); + } + } + + // Bind open trigger. + if (openBtn) { + openBtn.addEventListener('click', openPanel); + } + + // Bind close button. + if (closeBtn) { + closeBtn.addEventListener('click', closePanel); + } + + // Bind overlay backdrop click. + overlay.addEventListener('click', closePanel); + + // Close on Escape key. + document.addEventListener('keydown', function(e) { + if (e.key === 'Escape' && panel.classList.contains('ps-settings-panel--open')) { + closePanel(); + } + }); + + /** + * Contact form AJAX submission. + */ + if (contactForm) { + contactForm.addEventListener('submit', function(e) { + e.preventDefault(); + + if (!contactForm.checkValidity()) { + contactForm.reportValidity(); + return; + } + + var formData = new FormData(contactForm); + var btn = contactForm.querySelector('button[type="submit"]'); + var origText = btn.textContent; + + btn.disabled = true; + btn.textContent = '...'; + + if (statusEl) { + statusEl.innerHTML = ''; + } + + var xhr = new XMLHttpRequest(); + var ajaxUrl = (typeof peptideStarterData !== 'undefined' && peptideStarterData.ajaxUrl) + ? peptideStarterData.ajaxUrl + : '/wp-admin/admin-ajax.php'; + + xhr.open('POST', ajaxUrl); + xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + + xhr.onload = function() { + btn.disabled = false; + btn.textContent = origText; + + try { + var resp = JSON.parse(xhr.responseText); + if (resp.success) { + if (statusEl) { + statusEl.innerHTML = '

' + + escapeHtml(resp.data.message || 'Message sent.') + '

'; + } + contactForm.reset(); + } else { + if (statusEl) { + statusEl.innerHTML = '

' + + escapeHtml(resp.data.message || 'Failed to send.') + '

'; + } + } + } catch (ex) { + if (statusEl) { + statusEl.innerHTML = '

An unexpected error occurred.

'; + } + } + }; + + xhr.onerror = function() { + btn.disabled = false; + btn.textContent = origText; + if (statusEl) { + statusEl.innerHTML = '

Network error. Please try again.

'; + } + }; + + xhr.send(formData); + }); + } + + /** + * Escape HTML for safe insertion. + * @param {string} str - Raw text. + * @returns {string} Escaped HTML. + */ + function escapeHtml(str) { + var div = document.createElement('div'); + div.textContent = str; + return div.innerHTML; + } +})(); diff --git a/footer.php b/footer.php index 87ce9cf..fe439a5 100644 --- a/footer.php +++ b/footer.php @@ -2,46 +2,33 @@ /** * The footer for Peptide Starter theme * + * Updated footer with disclaimer, language links, and settings panel. + * + * @see template-parts/settings-panel.php — slide-out support panel + * @see template-parts/newsletter-signup.php — newsletter CTA (on front page) + * @see functions.php — newsletter handler, contact form handler + * * @package peptide-starter */ ?> -
+ + + + + + + diff --git a/front-page.php b/front-page.php index 806451d..49a7b52 100644 --- a/front-page.php +++ b/front-page.php @@ -2,6 +2,13 @@ /** * The front page template * + * Layout: Hero → Research Modules → PRAutoBlogger posts → Peptide News feed. + * Newsletter signup is handled by footer.php (via template part) on the front page. + * + * @see template-parts/module-cards.php — research modules grid + * @see template-parts/newsletter-signup.php — included via footer.php on front page + * @see functions.php — helper functions for hero content + * * @package peptide-starter */ @@ -15,8 +22,8 @@ get_header();

1 ) { $last_word = array_pop( $words ); echo esc_html( implode( ' ', $words ) ) . ' ' . esc_html( $last_word ) . ''; @@ -53,13 +60,16 @@ get_header(); - - + + + + +
@@ -86,12 +95,6 @@ get_header(); - - diff --git a/functions.php b/functions.php index de2f122..125baa3 100644 --- a/functions.php +++ b/functions.php @@ -2,42 +2,50 @@ /** * Peptide Starter Theme Functions * + * Core theme setup: supports, menus, scripts, customizer, helpers. + * Feature-specific logic is split into inc/ files to keep this under 300 lines. + * + * @see inc/auth-handlers.php — AJAX login/register handlers + * @see inc/contact-handler.php — AJAX contact form handler + * @see inc/page-setup.php — auto-create pages on theme activation + * @see inc/newsletter-admin.php — admin page for subscriber export + * * @package peptide-starter */ -// Exit if accessed directly +// Exit if accessed directly. if ( ! defined( 'ABSPATH' ) ) { exit; } -// Define constants -define( 'PEPTIDE_STARTER_VERSION', '1.4.1' ); +// Define constants. +define( 'PEPTIDE_STARTER_VERSION', '1.5.0' ); define( 'PEPTIDE_STARTER_DIR', get_template_directory() ); define( 'PEPTIDE_STARTER_URI', get_template_directory_uri() ); +// Load feature modules. +require_once PEPTIDE_STARTER_DIR . '/inc/helpers.php'; +require_once PEPTIDE_STARTER_DIR . '/inc/customizer.php'; +require_once PEPTIDE_STARTER_DIR . '/inc/auth-handlers.php'; +require_once PEPTIDE_STARTER_DIR . '/inc/contact-handler.php'; +require_once PEPTIDE_STARTER_DIR . '/inc/page-setup.php'; +require_once PEPTIDE_STARTER_DIR . '/inc/newsletter-admin.php'; + /** - * Set up theme defaults and register support for various WordPress features + * Set up theme defaults and register support for various WordPress features. + * + * @return void */ function peptide_starter_setup() { - // Add theme support add_theme_support( 'title-tag' ); add_theme_support( 'post-thumbnails' ); add_theme_support( 'responsive-embeds' ); add_theme_support( 'custom-logo' ); add_theme_support( 'html5', - array( - 'search-form', - 'comment-form', - 'comment-list', - 'gallery', - 'caption', - 'style', - 'script', - ) + array( 'search-form', 'comment-form', 'comment-list', 'gallery', 'caption', 'style', 'script' ) ); - // Register navigation menus register_nav_menus( array( 'primary' => esc_html__( 'Primary Menu', 'peptide-starter' ), @@ -45,36 +53,42 @@ function peptide_starter_setup() { ) ); - // Add support for wide blocks add_theme_support( 'align-wide' ); - - // Load text domain load_theme_textdomain( 'peptide-starter', PEPTIDE_STARTER_DIR . '/languages' ); } add_action( 'after_setup_theme', 'peptide_starter_setup' ); /** - * Register and enqueue stylesheets and scripts + * Register and enqueue stylesheets and scripts. + * + * @return void */ function peptide_starter_scripts() { - // Register and enqueue main stylesheet wp_enqueue_style( 'peptide-starter-style', PEPTIDE_STARTER_URI . '/style.css', array(), PEPTIDE_STARTER_VERSION ); - // Add conditional scripts wp_enqueue_script( 'peptide-starter-navigation', PEPTIDE_STARTER_URI . '/assets/js/navigation.js', array(), PEPTIDE_STARTER_VERSION, true ); wp_enqueue_script( 'peptide-starter-theme', PEPTIDE_STARTER_URI . '/assets/js/theme.js', array(), PEPTIDE_STARTER_VERSION, true ); + wp_enqueue_script( 'peptide-starter-settings-panel', PEPTIDE_STARTER_URI . '/assets/js/settings-panel.js', array(), PEPTIDE_STARTER_VERSION, true ); - // Pass PHP data to JavaScript wp_localize_script( 'peptide-starter-theme', 'peptideStarterData', array( - 'siteUrl' => home_url( '/' ), - 'isDarkMode' => get_theme_mod( 'dark_mode_default', false ), + 'siteUrl' => home_url( '/' ), + 'ajaxUrl' => admin_url( 'admin-ajax.php' ), + 'isDarkMode' => get_theme_mod( 'dark_mode_default', false ), ) ); - // Dequeue comment reply script if comments are not open + // Conditional scripts for specific page templates. + if ( is_page_template( 'page-documentation.php' ) ) { + wp_enqueue_script( 'peptide-starter-docs', PEPTIDE_STARTER_URI . '/assets/js/documentation.js', array(), PEPTIDE_STARTER_VERSION, true ); + } + + if ( is_page_template( 'page-auth.php' ) ) { + wp_enqueue_script( 'peptide-starter-auth', PEPTIDE_STARTER_URI . '/assets/js/auth.js', array(), PEPTIDE_STARTER_VERSION, true ); + } + if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) { wp_enqueue_script( 'comment-reply' ); } @@ -82,14 +96,14 @@ function peptide_starter_scripts() { add_action( 'wp_enqueue_scripts', 'peptide_starter_scripts' ); /** - * Enqueue plugin override styles at a late priority so they load AFTER plugin stylesheets. - * This ensures theme overrides (e.g. removing line-clamp on article excerpts) beat plugin CSS. + * Enqueue plugin override styles at late priority. + * + * @return void */ function peptide_starter_plugin_overrides() { wp_add_inline_style( 'peptide-starter-style', ' - /* Override Peptide News plugin line-clamp — show full summaries */ p.pn-article-excerpt, .pn-article-card p.pn-article-excerpt, body .pn-article-excerpt { @@ -104,402 +118,39 @@ function peptide_starter_plugin_overrides() { add_action( 'wp_enqueue_scripts', 'peptide_starter_plugin_overrides', 99 ); /** - * Register widget areas + * Register widget areas. + * + * @return void */ function peptide_starter_widgets_init() { - register_sidebar( - array( - 'name' => esc_html__( 'Footer - Column 1', 'peptide-starter' ), - 'id' => 'footer-1', - 'description' => esc_html__( 'Footer widget area 1', 'peptide-starter' ), - 'before_widget' => '', - 'before_title' => '

', - 'after_title' => '

', - ) - ); - - register_sidebar( - array( - 'name' => esc_html__( 'Footer - Column 2', 'peptide-starter' ), - 'id' => 'footer-2', - 'description' => esc_html__( 'Footer widget area 2', 'peptide-starter' ), - 'before_widget' => '', - 'before_title' => '

', - 'after_title' => '

', - ) - ); - - register_sidebar( - array( - 'name' => esc_html__( 'Footer - Column 3', 'peptide-starter' ), - 'id' => 'footer-3', - 'description' => esc_html__( 'Footer widget area 3', 'peptide-starter' ), - 'before_widget' => '', - 'before_title' => '

', - 'after_title' => '

', - ) - ); - - register_sidebar( - array( - 'name' => esc_html__( 'Footer - Column 4', 'peptide-starter' ), - 'id' => 'footer-4', - 'description' => esc_html__( 'Footer widget area 4', 'peptide-starter' ), - 'before_widget' => '', - 'before_title' => '

', - 'after_title' => '

', - ) - ); + for ( $i = 1; $i <= 4; $i++ ) { + register_sidebar( + array( + /* translators: %d: footer column number */ + 'name' => sprintf( esc_html__( 'Footer - Column %d', 'peptide-starter' ), $i ), + 'id' => 'footer-' . $i, + /* translators: %d: footer column number */ + 'description' => sprintf( esc_html__( 'Footer widget area %d', 'peptide-starter' ), $i ), + 'before_widget' => '', + 'before_title' => '

', + 'after_title' => '

', + ) + ); + } } add_action( 'widgets_init', 'peptide_starter_widgets_init' ); -/** - * Register theme customizer options - */ -function peptide_starter_customize_register( $wp_customize ) { - // Logo section - $wp_customize->add_section( - 'peptide_starter_branding', - array( - 'title' => esc_html__( 'Branding', 'peptide-starter' ), - 'priority' => 30, - ) - ); - - // Hero settings section - $wp_customize->add_section( - 'peptide_starter_hero', - array( - 'title' => esc_html__( 'Hero Section', 'peptide-starter' ), - 'priority' => 40, - ) - ); - - // Hero Title - $wp_customize->add_setting( - 'hero_title', - array( - 'default' => get_bloginfo( 'name' ), - 'transport' => 'postMessage', - 'sanitize_callback' => 'sanitize_text_field', - ) - ); - - $wp_customize->add_control( - 'hero_title', - array( - 'label' => esc_html__( 'Hero Title', 'peptide-starter' ), - 'section' => 'peptide_starter_hero', - 'type' => 'text', - ) - ); - - // Hero Subtitle - $wp_customize->add_setting( - 'hero_subtitle', - array( - 'default' => esc_html__( 'A scientific peptide reference database', 'peptide-starter' ), - 'transport' => 'postMessage', - 'sanitize_callback' => 'sanitize_text_field', - ) - ); - - $wp_customize->add_control( - 'hero_subtitle', - array( - 'label' => esc_html__( 'Hero Subtitle', 'peptide-starter' ), - 'section' => 'peptide_starter_hero', - 'type' => 'textarea', - ) - ); - - // Hero Search Placeholder - $wp_customize->add_setting( - 'hero_search_placeholder', - array( - 'default' => esc_html__( 'Search peptides, sequences, or research...', 'peptide-starter' ), - 'transport' => 'postMessage', - 'sanitize_callback' => 'sanitize_text_field', - ) - ); - - $wp_customize->add_control( - 'hero_search_placeholder', - array( - 'label' => esc_html__( 'Search Placeholder', 'peptide-starter' ), - 'section' => 'peptide_starter_hero', - 'type' => 'text', - ) - ); - - // Footer section - $wp_customize->add_section( - 'peptide_starter_footer', - array( - 'title' => esc_html__( 'Footer', 'peptide-starter' ), - 'priority' => 50, - ) - ); - - // Footer Copyright Text — allows safe HTML (links, bold, em) via wp_kses_post - $wp_customize->add_setting( - 'footer_copyright', - array( - 'default' => esc_html__( 'Copyright © 2026 Peptide Repo. All rights reserved.', 'peptide-starter' ), - 'transport' => 'postMessage', - 'sanitize_callback' => 'wp_kses_post', - ) - ); - - $wp_customize->add_control( - 'footer_copyright', - array( - 'label' => esc_html__( 'Copyright Text', 'peptide-starter' ), - 'section' => 'peptide_starter_footer', - 'type' => 'textarea', - ) - ); - - // Dark mode section - $wp_customize->add_section( - 'peptide_starter_theme_mode', - array( - 'title' => esc_html__( 'Theme Mode', 'peptide-starter' ), - 'priority' => 60, - ) - ); - - // Dark mode default - $wp_customize->add_setting( - 'dark_mode_default', - array( - 'default' => false, - 'transport' => 'refresh', - 'sanitize_callback' => 'peptide_starter_sanitize_checkbox', - ) - ); - - $wp_customize->add_control( - 'dark_mode_default', - array( - 'label' => esc_html__( 'Enable Dark Mode by Default', 'peptide-starter' ), - 'section' => 'peptide_starter_theme_mode', - 'type' => 'checkbox', - ) - ); -} -add_action( 'customize_register', 'peptide_starter_customize_register' ); - -/** - * Sanitize checkbox values for the Customizer. - * - * Returns true only for truthy values; everything else becomes false. - * Used as sanitize_callback for the dark_mode_default setting. - * - * @param mixed $checked The raw value from the Customizer. - * @return bool True if checked, false otherwise. - */ -function peptide_starter_sanitize_checkbox( $checked ) { - return ( ( isset( $checked ) && true === $checked ) ? true : false ); -} - -/** - * Add custom logo support - */ -function peptide_starter_get_custom_logo() { - if ( function_exists( 'get_custom_logo' ) ) { - return get_custom_logo(); - } - return ''; -} - -/** - * Navigation Menu Walker for handling active states - */ -class Peptide_Starter_Nav_Walker extends Walker_Nav_Menu { - public function start_el( &$output, $item, $depth = 0, $args = null, $id = 0 ) { - if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) { - $t = ''; - $n = ''; - } else { - $t = "\t"; - $n = "\n"; - } - $indent = ( $depth ) ? str_repeat( $t, $depth ) : ''; - - $classes = empty( $item->classes ) ? array() : (array) $item->classes; - $classes[] = 'menu-item-' . $item->ID; - - if ( in_array( 'current-menu-item', $classes, true ) || in_array( 'current-menu-parent', $classes, true ) ) { - $classes[] = 'active'; - } - - $args = apply_filters( 'nav_menu_item_args', $args, $item, $depth ); - - $class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args, $depth ) ); - $class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : ''; - - $id = apply_filters( 'nav_menu_item_id', 'menu-item-' . $item->ID, $item, $args, $depth ); - $id = $id ? ' id="' . esc_attr( $id ) . '"' : ''; - - $output .= $indent . ''; - - $atts = array(); - $atts['title'] = ! empty( $item->attr_title ) ? $item->attr_title : ''; - $atts['target'] = ! empty( $item->target ) ? $item->target : ''; - $atts['rel'] = ! empty( $item->xfn ) ? $item->xfn : ''; - $atts['href'] = ! empty( $item->url ) ? $item->url : ''; - - $atts = apply_filters( 'nav_menu_link_attributes', $atts, $item, $args, $depth ); - - $attributes = ''; - foreach ( $atts as $attr => $value ) { - if ( ! empty( $value ) ) { - $value = ( 'href' === $attr ) ? esc_url( $value ) : esc_attr( $value ); - $attributes .= ' ' . $attr . '="' . $value . '"'; - } - } - - $title = apply_filters( 'nav_menu_item_title', $item->title, $item, $args, $depth ); - $title = apply_filters( 'nav_menu_item_title', $title, $item, $args, $depth ); - - $link = $args->before; - $link .= ''; - $link .= $args->link_before . $title . $args->link_after; - $link .= ''; - $link .= $args->after; - - $output .= apply_filters( 'nav_menu_item_title', $link, $item, $args, $depth ); - - $output .= '' . $n; - } -} - -/** - * Display custom logo - */ -function peptide_starter_the_custom_logo() { - if ( function_exists( 'the_custom_logo' ) ) { - the_custom_logo(); - } else { - $site_name = get_bloginfo( 'name' ); - echo ''; - } -} - -/** - * Get hero title - */ -function peptide_starter_get_hero_title() { - return get_theme_mod( 'hero_title', get_bloginfo( 'name' ) ); -} - -/** - * Get hero subtitle - */ -function peptide_starter_get_hero_subtitle() { - return get_theme_mod( 'hero_subtitle', esc_html__( 'A scientific peptide reference database', 'peptide-starter' ) ); -} - -/** - * Get search placeholder - */ -function peptide_starter_get_search_placeholder() { - return get_theme_mod( 'hero_search_placeholder', esc_html__( 'Search peptides, sequences, or research...', 'peptide-starter' ) ); -} - -/** - * Get footer copyright text - */ -function peptide_starter_get_footer_copyright() { - return get_theme_mod( 'footer_copyright', esc_html__( 'Copyright © 2026 Peptide Repo. All rights reserved.', 'peptide-starter' ) ); -} - -/** - * Apply dark mode class to body if enabled - */ -function peptide_starter_dark_mode_body_class( $classes ) { - if ( get_theme_mod( 'dark_mode_default', false ) ) { - $classes[] = 'dark-mode-default'; - } - return $classes; -} -add_filter( 'body_class', 'peptide_starter_dark_mode_body_class' ); - -/** - * Register pagination function - */ -function peptide_starter_pagination() { - global $wp_query; - - if ( $wp_query->max_num_pages <= 1 ) { - return; - } - - $current_page = max( 1, get_query_var( 'paged' ) ); - $total_pages = $wp_query->max_num_pages; - - echo ''; -} - -/** - * Get the excerpt - */ -function peptide_starter_get_excerpt( $post_id = 0, $length = 20 ) { - if ( ! $post_id ) { - $post_id = get_the_ID(); - } - - $excerpt = get_the_excerpt( $post_id ); - - if ( ! $excerpt ) { - $excerpt = wp_trim_words( get_the_content( $post_id ), $length ); - } - - return $excerpt; -} - -/** - * Check if we should show footer newsletter signup - */ -function peptide_starter_show_newsletter_form() { - return apply_filters( 'peptide_starter_show_newsletter', true ); -} - /** * Handle newsletter signup form submission. + * Stores email in wp_options with duplicate check. * - * Validates the nonce and email, then fires an action hook so plugins - * can handle the actual subscription logic (e.g. Mailchimp, SendGrid). - * If no plugin handles it, redirects back with a generic success message. - * - * @see footer.php — renders the newsletter form + * @return void Redirects back with status parameter. */ function peptide_starter_handle_newsletter_signup() { - // Verify nonce — prevents CSRF attacks. if ( ! isset( $_POST['ps_newsletter_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['ps_newsletter_nonce'] ) ), 'peptide_starter_newsletter' ) ) { - wp_die( - esc_html__( 'Security check failed. Please try again.', 'peptide-starter' ), - esc_html__( 'Error', 'peptide-starter' ), - array( 'response' => 403 ) - ); + wp_die( esc_html__( 'Security check failed.', 'peptide-starter' ), esc_html__( 'Error', 'peptide-starter' ), array( 'response' => 403 ) ); } $email = isset( $_POST['email'] ) ? sanitize_email( wp_unslash( $_POST['email'] ) ) : ''; @@ -509,13 +160,21 @@ function peptide_starter_handle_newsletter_signup() { exit; } - /** - * Fires when a valid newsletter signup is submitted. - * - * Plugins should hook here to send the email to their mailing list provider. - * - * @param string $email The validated subscriber email address. - */ + // Store in wp_options with duplicate check. + $emails = get_option( 'ps_newsletter_emails', array() ); + $existing_emails = array_column( $emails, 'email' ); + + if ( in_array( $email, $existing_emails, true ) ) { + wp_safe_redirect( add_query_arg( 'ps_newsletter', 'duplicate', wp_get_referer() ) ); + exit; + } + + $emails[] = array( + 'email' => $email, + 'date' => current_time( 'Y-m-d H:i:s' ), + ); + update_option( 'ps_newsletter_emails', $emails ); + do_action( 'peptide_starter_newsletter_subscribe', $email ); wp_safe_redirect( add_query_arg( 'ps_newsletter', 'success', wp_get_referer() ) ); @@ -525,29 +184,23 @@ add_action( 'admin_post_peptide_starter_newsletter_signup', 'peptide_starter_han add_action( 'admin_post_nopriv_peptide_starter_newsletter_signup', 'peptide_starter_handle_newsletter_signup' ); /** - * Enqueue inline script for dark mode toggle (output in footer) + * Inline dark mode script in wp_head — prevents flash of wrong theme. + * + * @return void */ function peptide_starter_inline_dark_mode_script() { $default_dark = get_theme_mod( 'dark_mode_default', false ) ? 'dark' : 'light'; ?>
- - - - + + + +
+ +
+ + + + +
+
+ + + + + + + + + + + + + + + + + EN + + + + + +

+ + +

+
+ + +
+

+ +
+ + + + +
+ + + + + +
+ +
+ + +
+ +
+ + +
+
+ + +
+ +

+ + +

+
+ + +
+ + + diff --git a/page-calculator.php b/page-calculator.php new file mode 100644 index 0000000..e81f290 --- /dev/null +++ b/page-calculator.php @@ -0,0 +1,48 @@ + + +
+
+
+ +
+

+

+ + + +
+ +
+
+
+ + diff --git a/page-directory.php b/page-directory.php new file mode 100644 index 0000000..3543bf8 --- /dev/null +++ b/page-directory.php @@ -0,0 +1,48 @@ + + +
+
+
+ +
+

+

+ + + +
+ +
+
+
+ + diff --git a/page-documentation.php b/page-documentation.php new file mode 100644 index 0000000..db82469 --- /dev/null +++ b/page-documentation.php @@ -0,0 +1,65 @@ + + +
+
+ + + + +
+
+ +
+

+ +

+ +
+ +
+ +
+ +
+
+
+
+ + diff --git a/page-profile.php b/page-profile.php new file mode 100644 index 0000000..cfc5599 --- /dev/null +++ b/page-profile.php @@ -0,0 +1,63 @@ + + +
+
+
+ +
+

display_name ) ); ?>

+

+ +
+ +
+
+
+ + diff --git a/page-protocol-builder.php b/page-protocol-builder.php new file mode 100644 index 0000000..27f1142 --- /dev/null +++ b/page-protocol-builder.php @@ -0,0 +1,48 @@ + + +
+
+
+ +
+

+

+ + + +
+ +
+
+
+ + diff --git a/page-science-feed.php b/page-science-feed.php new file mode 100644 index 0000000..66f441a --- /dev/null +++ b/page-science-feed.php @@ -0,0 +1,56 @@ + + +
+
+
+ +
+

+

+
+ +
+

+

+ + + +
+ +
+
+ + +
+ + diff --git a/page-subject-log.php b/page-subject-log.php new file mode 100644 index 0000000..3f0935a --- /dev/null +++ b/page-subject-log.php @@ -0,0 +1,48 @@ + + +
+
+
+ +
+

+

+ + + +
+ +
+
+
+ + diff --git a/page-tracker.php b/page-tracker.php new file mode 100644 index 0000000..1a5098e --- /dev/null +++ b/page-tracker.php @@ -0,0 +1,48 @@ + + +
+
+
+ +
+

+

+ + + +
+ +
+
+
+ + diff --git a/style.css b/style.css index 494cc58..0411845 100644 --- a/style.css +++ b/style.css @@ -4,7 +4,7 @@ Theme URI: https://github.com/peptiderepo/peptide-starter Description: Premium, accessible WordPress theme for scientific peptide reference database Author: Peptide Repo Author URI: https://peptiderepo.com -Version: 1.4.1 +Version: 1.5.0 License: GPL v2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html Text Domain: peptide-starter @@ -2254,6 +2254,826 @@ html[data-theme="light"] { flex-wrap: wrap; } +/* ============================================================================ + 14. TOOL PAGES — Generic wrapper for plugin shortcode templates + ============================================================================ */ + +.ps-tool-page { + padding: var(--spacing-3xl) var(--spacing-lg); + min-height: 60vh; +} + +.ps-tool-fallback { + text-align: center; + padding: var(--spacing-4xl) 0; +} + +.ps-tool-fallback h1 { + margin-bottom: var(--spacing-md); +} + +.ps-tool-fallback p { + color: var(--color-text-secondary); + margin-bottom: var(--spacing-2xl); +} + +/* ============================================================================ + 15. RESEARCH MODULE CARDS — Front page grid + ============================================================================ */ + +.ps-modules-section { + padding: var(--spacing-3xl) var(--spacing-lg); + background-color: var(--color-bg-secondary); +} + +.ps-modules-header { + text-align: center; + margin-bottom: var(--spacing-2xl); +} + +.ps-modules-title { + font-size: var(--text-h2); + margin-bottom: var(--spacing-sm); + margin-top: 0; +} + +.ps-modules-subtitle { + color: var(--color-text-secondary); + font-size: var(--text-body-md); +} + +.ps-modules-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: var(--spacing-lg); + max-width: var(--container-2xl); + margin: 0 auto; +} + +.ps-module-card { + display: flex; + flex-direction: column; + padding: var(--spacing-xl); + background-color: var(--color-bg-primary); + border: 1px solid var(--color-border-default); + border-radius: var(--radius-lg); + text-decoration: none; + color: var(--color-text-primary); + transition: all var(--transition-normal); +} + +.ps-module-card:hover { + border-color: var(--color-brand-primary); + box-shadow: 0 4px 16px rgba(0, 102, 204, 0.1); + transform: translateY(-2px); +} + +.ps-module-card--inactive { + opacity: 0.5; + pointer-events: none; +} + +.ps-module-card__icon { + display: flex; + align-items: center; + justify-content: center; + width: 48px; + height: 48px; + border-radius: 50%; + background-color: rgba(0, 102, 204, 0.1); + color: var(--color-brand-primary); + margin-bottom: var(--spacing-md); +} + +.ps-module-card__title { + font-size: var(--text-h4); + font-weight: 600; + margin-bottom: var(--spacing-sm); + margin-top: 0; +} + +.ps-module-card__description { + font-size: var(--text-body-md); + color: var(--color-text-secondary); + line-height: 1.6; + flex: 1; + margin-bottom: var(--spacing-md); +} + +.ps-module-card__link { + font-size: var(--text-body-sm); + font-weight: 600; + color: var(--color-brand-primary); +} + +/* ============================================================================ + 16. DOCUMENTATION PAGE — Two-column layout with ToC sidebar + ============================================================================ */ + +.ps-docs-layout { + display: grid; + grid-template-columns: 240px 1fr; + gap: var(--spacing-2xl); + max-width: var(--container-2xl); + margin: 0 auto; + align-items: start; +} + +.ps-docs-sidebar { + position: sticky; + top: 100px; + max-height: calc(100vh - 120px); + overflow-y: auto; + padding: var(--spacing-lg) 0; +} + +.ps-docs-sidebar-inner { + padding-right: var(--spacing-lg); + border-right: 1px solid var(--color-border-subtle); +} + +.ps-docs-toc-title { + font-size: var(--text-h6); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--color-text-secondary); + margin-bottom: var(--spacing-md); + margin-top: 0; + cursor: default; +} + +.ps-docs-toc ul { + list-style: none; + display: flex; + flex-direction: column; + gap: var(--spacing-xs); +} + +.ps-docs-toc-link { + display: block; + padding: var(--spacing-xs) var(--spacing-sm); + font-size: var(--text-body-sm); + color: var(--color-text-secondary); + text-decoration: none; + border-radius: var(--radius-sm); + transition: all var(--transition-fast); + border-left: 2px solid transparent; +} + +.ps-docs-toc-link:hover { + color: var(--color-brand-primary); + background-color: var(--color-bg-secondary); +} + +.ps-docs-toc-link--active { + color: var(--color-brand-primary); + border-left-color: var(--color-brand-primary); + background-color: var(--color-bg-secondary); + font-weight: 500; +} + +.ps-docs-content { + min-width: 0; +} + +.ps-docs-header { + margin-bottom: var(--spacing-2xl); +} + +.ps-docs-header h1 { + margin-bottom: var(--spacing-md); +} + +.ps-docs-intro { + font-size: var(--text-body-lg); + color: var(--color-text-secondary); + line-height: 1.7; +} + +.ps-docs-body h2 { + margin-top: var(--spacing-3xl); + padding-top: var(--spacing-lg); + border-top: 1px solid var(--color-border-subtle); +} + +.ps-docs-body h2:first-child { + margin-top: 0; + padding-top: 0; + border-top: none; +} + +/* Documentation step styling for SOPs */ +.ps-docs-step { + counter-increment: step-counter; + position: relative; + padding-left: var(--spacing-2xl); + margin-bottom: var(--spacing-xl); +} + +.ps-docs-step::before { + content: counter(step-counter); + position: absolute; + left: 0; + top: 0; + width: 28px; + height: 28px; + background-color: var(--color-brand-primary); + color: white; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: var(--text-body-sm); + font-weight: 700; +} + +/* Warning callout for docs */ +.ps-docs-warning { + padding: var(--spacing-lg); + background-color: #FEF3C7; + border-left: 4px solid var(--color-status-warning); + border-radius: var(--radius-md); + margin: var(--spacing-lg) 0; + color: #78350F; +} + +/* Documentation tables */ +.ps-docs-table { + width: 100%; + border-collapse: collapse; + margin: var(--spacing-lg) 0; +} + +.ps-docs-table th, +.ps-docs-table td { + padding: var(--spacing-sm) var(--spacing-md); + border: 1px solid var(--color-border-default); + text-align: left; + font-size: var(--text-body-md); +} + +.ps-docs-table th { + background-color: var(--color-bg-secondary); + font-weight: 600; +} + +/* Mobile: collapse sidebar to dropdown */ +@media (max-width: 1023px) { + .ps-docs-layout { + grid-template-columns: 1fr; + } + + .ps-docs-sidebar { + position: static; + max-height: none; + border-bottom: 1px solid var(--color-border-subtle); + padding-bottom: var(--spacing-md); + margin-bottom: var(--spacing-lg); + } + + .ps-docs-sidebar-inner { + border-right: none; + padding-right: 0; + } + + .ps-docs-toc-title { + cursor: pointer; + } + + .ps-docs-toc-title::after { + content: ' ▾'; + } + + .ps-docs-toc { + display: none; + } + + .ps-docs-sidebar--open .ps-docs-toc { + display: block; + } +} + +/* ============================================================================ + 17. AUTH PAGE — Sign-in / Register forms + ============================================================================ */ + +.ps-auth-page { + display: flex; + align-items: center; + justify-content: center; + min-height: 70vh; +} + +.ps-auth-container { + max-width: 440px; +} + +.ps-auth-card { + background-color: var(--color-bg-primary); + border: 1px solid var(--color-border-default); + border-radius: var(--radius-lg); + padding: var(--spacing-2xl); + box-shadow: var(--shadow-md); +} + +.ps-auth-card h1 { + font-size: var(--text-h3); + margin-bottom: var(--spacing-lg); +} + +.ps-auth-info { + margin-bottom: var(--spacing-lg); +} + +.ps-auth-info p { + margin-bottom: 0; + font-size: var(--text-body-sm); +} + +.ps-auth-form .ps-form-group { + margin-bottom: var(--spacing-md); +} + +.ps-auth-link { + text-align: center; + margin-top: var(--spacing-md); + font-size: var(--text-body-sm); +} + +.ps-auth-toggle { + text-align: center; + margin-top: var(--spacing-lg); + padding-top: var(--spacing-lg); + border-top: 1px solid var(--color-border-subtle); + font-size: var(--text-body-sm); + color: var(--color-text-secondary); +} + +.ps-auth-toggle a { + color: var(--color-brand-primary); + font-weight: 500; +} + +.ps-auth-form-wrap--hidden { + display: none; +} + +.ps-auth-messages { + margin-bottom: var(--spacing-md); +} + +.ps-form-hint { + display: block; + font-size: var(--text-caption); + color: var(--color-text-tertiary); + margin-top: 4px; +} + +/* Password strength */ +.ps-password-strength { + margin-top: 4px; + font-size: var(--text-caption); + font-weight: 500; +} + +.ps-strength-weak { color: var(--color-status-error); } +.ps-strength-fair { color: var(--color-status-warning); } +.ps-strength-strong { color: var(--color-status-success); } + +/* ============================================================================ + 18. NEWSLETTER SECTION — Standalone signup CTA + ============================================================================ */ + +.ps-newsletter-section { + background-color: var(--color-bg-tertiary); + padding: var(--spacing-3xl) var(--spacing-lg); +} + +.ps-newsletter-inner { + max-width: var(--container-md); + margin: 0 auto; + text-align: center; +} + +.ps-newsletter-title { + font-size: var(--text-h2); + margin-bottom: var(--spacing-sm); + margin-top: 0; +} + +.ps-newsletter-subtitle { + color: var(--color-text-secondary); + margin-bottom: var(--spacing-lg); +} + +.ps-newsletter-form-row { + display: flex; + gap: var(--spacing-sm); + max-width: 480px; + margin: 0 auto; +} + +.ps-newsletter-input { + flex: 1; + min-width: 0; +} + +.ps-newsletter-status { + margin-top: var(--spacing-md); + text-align: left; + max-width: 480px; + margin-left: auto; + margin-right: auto; +} + +.ps-newsletter-learn-more { + margin-top: var(--spacing-md); + font-size: var(--text-body-sm); +} + +.ps-newsletter-learn-more a { + color: var(--color-brand-primary); +} + +@media (max-width: 479px) { + .ps-newsletter-form-row { + flex-direction: column; + } +} + +/* ============================================================================ + 19. USER MENU — Logged-in dropdown in header + ============================================================================ */ + +.ps-user-menu { + position: relative; +} + +.ps-user-menu__toggle { + display: flex; + align-items: center; + gap: var(--spacing-xs); + background: none; + border: 1px solid var(--color-border-default); + border-radius: var(--radius-md); + padding: var(--spacing-sm) var(--spacing-md); + font-size: var(--text-body-sm); + font-weight: 500; + color: var(--color-text-primary); + cursor: pointer; + transition: all var(--transition-fast); +} + +.ps-user-menu__toggle:hover { + border-color: var(--color-brand-primary); +} + +.ps-user-menu__name { + max-width: 120px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.ps-user-menu__dropdown { + display: none; + position: absolute; + top: 100%; + right: 0; + margin-top: var(--spacing-xs); + background-color: var(--color-bg-primary); + border: 1px solid var(--color-border-default); + border-radius: var(--radius-md); + box-shadow: var(--shadow-lg); + min-width: 180px; + z-index: var(--z-dropdown); + padding: var(--spacing-xs) 0; +} + +.ps-user-menu:hover .ps-user-menu__dropdown, +.ps-user-menu__toggle:focus + .ps-user-menu__dropdown { + display: block; +} + +.ps-user-menu__dropdown a { + display: block; + padding: var(--spacing-sm) var(--spacing-md); + font-size: var(--text-body-sm); + color: var(--color-text-primary); + text-decoration: none; + transition: background-color var(--transition-fast); +} + +.ps-user-menu__dropdown a:hover { + background-color: var(--color-bg-secondary); + color: var(--color-brand-primary); +} + +/* ============================================================================ + 20. SETTINGS PANEL — Slide-out support panel + ============================================================================ */ + +.ps-settings-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.4); + z-index: var(--z-modal-backdrop); + opacity: 0; + visibility: hidden; + transition: opacity var(--transition-slow), visibility var(--transition-slow); +} + +.ps-settings-overlay--active { + opacity: 1; + visibility: visible; +} + +.ps-settings-panel { + position: fixed; + top: 0; + right: -400px; + width: 380px; + max-width: 90vw; + height: 100vh; + background-color: var(--color-bg-primary); + border-left: 1px solid var(--color-border-default); + box-shadow: var(--shadow-xl); + z-index: var(--z-modal); + overflow-y: auto; + transition: right var(--transition-slow); + display: flex; + flex-direction: column; +} + +.ps-settings-panel--open { + right: 0; +} + +.ps-settings-panel__header { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--spacing-lg) var(--spacing-xl); + border-bottom: 1px solid var(--color-border-subtle); +} + +.ps-settings-panel__header h2 { + font-size: var(--text-h4); + margin: 0; +} + +.ps-settings-panel__close { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + background: none; + border: none; + color: var(--color-text-secondary); + cursor: pointer; + border-radius: var(--radius-sm); + transition: all var(--transition-fast); +} + +.ps-settings-panel__close:hover { + color: var(--color-text-primary); + background-color: var(--color-bg-secondary); +} + +.ps-settings-panel__body { + padding: var(--spacing-xl); + flex: 1; +} + +.ps-settings-section { + margin-bottom: var(--spacing-2xl); +} + +.ps-settings-section h3 { + font-size: var(--text-h5); + margin-bottom: var(--spacing-md); + margin-top: 0; +} + +.ps-settings-disclaimer { + font-size: var(--text-body-sm); + color: var(--color-text-secondary); +} + +.ps-settings-disclaimer p { + font-size: var(--text-body-sm); + line-height: 1.6; +} + +.ps-contact-form .ps-form-group { + margin-bottom: var(--spacing-md); +} + +.ps-contact-status { + margin-top: var(--spacing-md); +} + +/* ============================================================================ + 21. LANGUAGE PLACEHOLDER — Header icon + ============================================================================ */ + +.ps-lang-placeholder { + display: flex; + align-items: center; + gap: 2px; + color: var(--color-text-secondary); + font-size: var(--text-body-sm); + font-weight: 500; + cursor: default; +} + +.ps-lang-code { + font-size: var(--text-label); +} + +@media (max-width: 767px) { + .ps-lang-placeholder { + display: none; + } +} + +/* ============================================================================ + 22. FOOTER UPDATES — Disclaimer and language links + ============================================================================ */ + +.footer-disclaimer { + font-size: var(--text-caption); + color: var(--color-text-tertiary); + max-width: var(--container-md); + margin: var(--spacing-md) auto; + line-height: 1.5; +} + +.footer-languages { + display: flex; + align-items: center; + justify-content: center; + gap: var(--spacing-md); + margin-top: var(--spacing-md); + font-size: var(--text-body-sm); + flex-wrap: wrap; +} + +.footer-languages-label { + font-weight: 500; + color: var(--color-text-secondary); +} + +.footer-languages a { + color: var(--color-brand-primary); +} + +/* ============================================================================ + 23. PROFILE PAGE — Basic profile fallback + ============================================================================ */ + +.ps-profile-basic { + text-align: center; + padding: var(--spacing-2xl) 0; +} + +.ps-profile-basic h1 { + margin-bottom: var(--spacing-md); +} + +.ps-profile-links { + display: flex; + gap: var(--spacing-md); + justify-content: center; + flex-wrap: wrap; + margin-top: var(--spacing-xl); +} + +/* Science Feed header */ +.ps-science-feed-header { + text-align: center; + margin-bottom: var(--spacing-2xl); +} + +.ps-science-feed-header h1 { + margin-bottom: var(--spacing-md); +} + +.ps-science-feed-header p { + color: var(--color-text-secondary); +} + +/* ============================================================================ + 24. DARK MODE — Additions for new components + ============================================================================ */ + +html[data-theme="dark"] .ps-module-card__icon { + background-color: rgba(59, 130, 246, 0.15); +} + +html[data-theme="dark"] .ps-module-card:hover { + box-shadow: 0 4px 16px rgba(59, 130, 246, 0.15); +} + +html[data-theme="dark"] .ps-docs-warning { + background-color: #78350F; + border-color: #F59E0B; + color: #FEF3C7; +} + +html[data-theme="dark"] .ps-auth-card { + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); +} + +html[data-theme="dark"] .ps-newsletter-section { + background-color: var(--color-bg-secondary); +} + +html[data-theme="dark"] .ps-settings-overlay { + background: rgba(0, 0, 0, 0.6); +} + +html[data-theme="dark"] .ps-modules-section { + background-color: var(--color-bg-secondary); +} + +/* Dropdown submenu support for primary navigation */ +.primary-navigation li.menu-item-has-children { + position: relative; +} + +.primary-navigation .sub-menu { + display: none; + position: absolute; + top: 100%; + left: 0; + background-color: var(--color-bg-primary); + border: 1px solid var(--color-border-default); + border-radius: var(--radius-md); + box-shadow: var(--shadow-lg); + min-width: 200px; + z-index: var(--z-dropdown); + padding: var(--spacing-xs) 0; + margin-top: var(--spacing-sm); +} + +.primary-navigation li.menu-item-has-children:hover > .sub-menu { + display: flex; + flex-direction: column; +} + +.primary-navigation .sub-menu li { + display: block; +} + +.primary-navigation .sub-menu a { + display: block; + padding: var(--spacing-sm) var(--spacing-md); + font-size: var(--text-body-sm); + white-space: nowrap; +} + +.primary-navigation .sub-menu a:hover { + background-color: var(--color-bg-secondary); +} + +.primary-navigation .sub-menu a::after { + display: none; +} + +/* ============================================================================ + 25. RESPONSIVE — Additions for new components + ============================================================================ */ + +@media (max-width: 767px) { + .ps-user-menu__toggle { + padding: var(--spacing-xs) var(--spacing-sm); + font-size: var(--text-caption); + } + + .ps-user-menu__name { + display: none; + } + + .ps-modules-grid { + grid-template-columns: 1fr; + } +} + +@media (min-width: 768px) { + .ps-modules-grid { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (min-width: 1024px) { + .ps-modules-grid { + grid-template-columns: repeat(3, 1fr); + } +} + /* ============================================================================ END OF STYLESHEET ============================================================================ */ diff --git a/template-parts/module-cards.php b/template-parts/module-cards.php new file mode 100644 index 0000000..7fbba71 --- /dev/null +++ b/template-parts/module-cards.php @@ -0,0 +1,98 @@ + __( 'Peptides', 'peptide-starter' ), + 'description' => __( 'Browse the complete reagent directory with protocols and research parameters.', 'peptide-starter' ), + 'url' => '/peptides', + 'icon' => '', + 'available' => shortcode_exists( 'peptide_directory' ) || post_type_exists( 'peptide' ), + ), + array( + 'title' => __( 'Calculator', 'peptide-starter' ), + 'description' => __( 'Precision reconstitution calculator for accurate laboratory preparations.', 'peptide-starter' ), + 'url' => '/calculator', + 'icon' => '', + 'available' => shortcode_exists( 'peptide_tools_calculator' ), + ), + array( + 'title' => __( 'Protocol Builder', 'peptide-starter' ), + 'description' => __( 'Interactive multi-step wizard for designing research protocols.', 'peptide-starter' ), + 'url' => '/protocol-builder', + 'icon' => '', + 'available' => shortcode_exists( 'peptide_tools_protocol_builder' ), + ), + array( + 'title' => __( 'Tracker', 'peptide-starter' ), + 'description' => __( 'Log doses, manage active compounds, and forecast vial depletion timelines.', 'peptide-starter' ), + 'url' => '/tracker', + 'icon' => '', + 'available' => shortcode_exists( 'peptide_tracker' ), + ), + array( + 'title' => __( 'Science Feed', 'peptide-starter' ), + 'description' => __( 'Latest research publications, clinical studies, and laboratory insights.', 'peptide-starter' ), + 'url' => '/news', + 'icon' => '', + 'available' => shortcode_exists( 'peptide_news' ), + ), + array( + 'title' => __( 'Subject Log', 'peptide-starter' ), + 'description' => __( 'Longitudinal tracking for biomarker data and protocol history.', 'peptide-starter' ), + 'url' => '/subject-log', + 'icon' => '', + 'available' => shortcode_exists( 'peptide_tracker_subject_log' ), + ), +); +?> + +
+
+
+

+

+ +

+
+ + +
+
diff --git a/template-parts/newsletter-signup.php b/template-parts/newsletter-signup.php new file mode 100644 index 0000000..a1fd933 --- /dev/null +++ b/template-parts/newsletter-signup.php @@ -0,0 +1,82 @@ + + +
+
+
+

+ +

+

+ +

+ + + + + +
+ + +
+ + +
+
+ +

+ + → + +

+
+
+
diff --git a/template-parts/settings-panel.php b/template-parts/settings-panel.php new file mode 100644 index 0000000..2e1f519 --- /dev/null +++ b/template-parts/settings-panel.php @@ -0,0 +1,102 @@ +display_name : ''; +$user_email = is_user_logged_in() ? $current_user->user_email : ''; +?> + + + + + +