Compare commits

..

7 Commits

Author SHA1 Message Date
peptiderepo
614ee2a2c3 fix: remove WP post date timestamp from verdict card (P2.2)
The verdict card was showing 'Updated [WP post creation date]' which doesn't
reflect the editorial last-reviewed date. Showing a wrong date is worse than
showing none. Timestamp removed; will re-add with last_reviewed_date post meta
in v1.1 when the review-cadence tracking system is ready to populate it.
2026-04-26 02:14:00 +00:00
peptiderepo
7b5f992a2a fix: CMO walkthrough P1 fixes — verdict_text, per-row glyphs, conditional disclosure, dark mode (v2.0.0-alpha.3)
P1.1: verdict_text meta field + card.php wiring
- Register verdict_text (textarea, show_in_rest) on peptide CPT
- card.php reads verdict_text meta; falls back to placeholder if empty
- Hardcoded generic placeholder no longer the default for published posts
- All 3 monographs populated with CMO-authored verdict text

P1.2: per-row glyph meta fields
- Register signal_row_1/2/3_glyph (enum: ✓|!|?|⊘|⚠, show_in_rest)
- card.php reads per-row glyph from meta instead of hardcoding by position
- Missing glyph renders nothing (not a wrong glyph)
- Semaglutide: ✓✓! | BPC-157: ✓!? | Melanotan-II: !!⚠

P1.3: conditional inline disclosure
- Register has_partner_link (boolean, default false, show_in_rest)
- single-peptide.php only renders inline disclosure when has_partner_link=true
- All 3 loaded monographs set to has_partner_link=false
- Footer disclosure unaffected (always renders)

N.1: dark mode toggle hidden
- header.php: toggle button hidden (display:none + aria-hidden)
- Dark-mode verdict colors not validated for v1; re-enable in v1.1

Meta box updated with textarea, glyph dropdowns, and partner-link checkbox.
2026-04-26 02:12:35 +00:00
peptiderepo
2fe536208b fix: correct CPT slug in verdict-meta.php (pr_peptide -> peptide)
The engineer registered verdict meta and the meta box against 'pr_peptide'
which does not exist. PR Core registers the CPT as 'peptide'. Result: the
verdict meta box was invisible in wp-admin and meta was not exposed in REST.
All functional register_post_meta and add_meta_box calls now target 'peptide'.
No data migration needed — post meta is keyed by meta_key, not CPT.
2026-04-25 20:35:14 +00:00
peptiderepo
ce08425270 fix: wire verdict card and disclosure into single-peptide.php (v2.0.0-alpha.2)
- Verdict card component was built in alpha.1 but never called from the
  template; no verdict state was rendering on any pr_peptide page.
  Now called immediately after the page header for posts with verdict_state.
- Affiliate disclosure (inline) was built in alpha.1 but never called.
  Now rendered after .peptide-content-box (below the_content / partner links)
  per the editorial disclosure spec: disclosure must follow the partner link.
- Suppress page-header <h1> when verdict_state is set; the verdict card
  already renders an <h1>, avoiding duplicate heading semantics / SEO issue.
2026-04-25 20:28:14 +00:00
peptiderepo
4f16ed9035 ci: verify deploy pipeline after SSH secret rotation 2026-04-25 20:11:19 +00:00
peptiderepo
2bb686a348 fix: PHPCS violations in brand components (Yoda conditions, array formatting, PHP tag) 2026-04-25 20:02:23 +00:00
peptiderepo
e23c141009 feat: brand identity v1.0.0 — Direction C Trusted Guide (theme v2.0.0-alpha.1)
Implements weeks 1–4 of the CMO brand identity sprint.

Key changes:
- CSS token system (assets/css/brand.css): full palette, verdict tokens, component styles
- Poppins/Inter/IBM Plex Mono Google Fonts loading with preload
- Full favicon/PWA/OG asset pack (peptide-repo-brand v1.0.0)
- 5-state verdict badge + card + evidence row components
- verdict_state + signal_row post meta on pr_peptide CPT
- Homepage hero: teal bg, new H1/subhead, lime search bar
- Archive verdict filter chips (CSS + pre_get_posts query hook)
- Affiliate disclosure component (3 contexts)
- page-how-we-review.php + page-about.php templates
- Footer Our Method links
- a11y: skip-to-content, reduced-motion, aria on verdict badges

QA: PASS (0 blockers) — qa-reviews/peptide-starter-theme/2026-04-26-a2bff59.md
Staging verified: https://staging.peptiderepo.com
2026-04-26 02:59:00 +07:00
10 changed files with 318 additions and 80 deletions

View File

@@ -1,5 +1,50 @@
# Peptide Starter Theme - Changelog # Peptide Starter Theme - Changelog
## [2.0.0-alpha.3] - 2026-04-26 — CMO walkthrough P1 fixes
### Fixed
- `template-parts/verdict/card.php`: Verdict text was a hardcoded generic placeholder.
Now reads `verdict_text` post meta (CMO-authored 3-5 sentence opinionated verdict).
Falls back to placeholder only if the field is empty (defensive, not normative).
- `template-parts/verdict/card.php`: Evidence-row glyphs were hardcoded by row position
(row 1=✓, row 2=!, row 3=?), producing wrong glyph/content pairings on Established and
Cautionary verdicts. Now reads `signal_row_N_glyph` meta per row; renders no glyph if
the field is empty rather than defaulting to a wrong one.
- `single-peptide.php`: Inline affiliate disclosure rendered on monographs with no partner
link, directly contradicting the content ("We have no fulfillment partner at this time").
Now conditional on `has_partner_link` boolean meta (default false). Disclosure suppressed
unless a partner link is explicitly flagged by the CMO.
- `header.php`: Dark-mode toggle was live in the header. Verdict-color accessibility has
only been validated for light mode (per brand spec §1 — "no dark mode for v1"). Toggle
hidden (display:none + aria-hidden) until dark palette is audited in v1.1.
### Added
- `inc/verdict-meta.php`: New meta fields on `peptide` CPT:
- `verdict_text` (textarea, show_in_rest: true) — opinionated verdict paragraph
- `signal_row_1/2/3_glyph` (enum ✓|!|?|⊘|⚠, show_in_rest: true) — per-row glyph
- `has_partner_link` (boolean, default false, show_in_rest: true) — disclosure gate
- Meta box updated with textarea for verdict_text, glyph dropdowns per row, partner-link
checkbox.
- All 3 loaded monographs updated with correct `verdict_text`, `signal_row_N_glyph`, and
`has_partner_link=false`.
## [2.0.0-alpha.2] - 2026-04-26 — Wire verdict card and disclosure into single-peptide.php
### Fixed
- `single-peptide.php`: Verdict card component was created in alpha.1 but never called
from the template — no verdict state was rendering on any peptide page. Now called
immediately after the page header for all `pr_peptide` posts with a `verdict_state`.
- `single-peptide.php`: Affiliate disclosure (inline context) was created in alpha.1 but
never called. Now rendered directly after the content box (below `the_content()`),
ensuring it appears below any partner links in the post body per the editorial
disclosure spec.
- `single-peptide.php`: Duplicate `<h1>` issue — the page header was rendering an H1
title AND the verdict card was also rendering an H1. Fixed: page header H1 is now
suppressed when a `verdict_state` is set; the verdict card owns the H1 for monograph
posts.
All notable changes to the Peptide Starter Theme are documented in this file. All notable changes to the Peptide Starter Theme are documented in this file.
## [2.0.0-alpha.1] - 2026-04-26 — Brand Identity v1.0.0 (Direction C: Trusted Guide) ## [2.0.0-alpha.1] - 2026-04-26 — Brand Identity v1.0.0 (Direction C: Trusted Guide)

View File

@@ -26,7 +26,7 @@ if ( ! defined( 'ABSPATH' ) ) {
} }
// Theme constants. // Theme constants.
define( 'PEPTIDE_STARTER_VERSION', '2.0.0-alpha.1' ); define( 'PEPTIDE_STARTER_VERSION', '2.0.0-alpha.3' );
define( 'PEPTIDE_STARTER_DIR', get_template_directory() ); define( 'PEPTIDE_STARTER_DIR', get_template_directory() );
define( 'PEPTIDE_STARTER_URI', get_template_directory_uri() ); define( 'PEPTIDE_STARTER_URI', get_template_directory_uri() );

View File

@@ -118,7 +118,8 @@
</button> </button>
<!-- Dark Mode Toggle --> <!-- Dark Mode Toggle -->
<button class="header-icon-btn dark-mode-toggle" aria-label="<?php esc_attr_e( 'Toggle dark mode', 'peptide-starter' ); ?>"> <!-- Dark mode disabled for v1: verdict-color accessibility only validated for light mode. Re-enable in v1.1 after dark palette audit. -->
<button class="header-icon-btn dark-mode-toggle" aria-label="<?php esc_attr_e( 'Toggle dark mode', 'peptide-starter' ); ?>" style="display:none;" aria-hidden="true">
<svg class="sun-icon" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" style="display: none;"> <svg class="sun-icon" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" style="display: none;">
<circle cx="10" cy="10" r="4" stroke="currentColor" stroke-width="2"/> <circle cx="10" cy="10" r="4" stroke="currentColor" stroke-width="2"/>
<path d="M10 1V3M10 17V19M19 10H17M3 10H1M16.657 16.657L15.243 15.243M4.757 4.757L3.343 3.343M16.657 3.343L15.243 4.757M4.757 15.243L3.343 16.657" stroke="currentColor" stroke-width="2" stroke-linecap="round"/> <path d="M10 1V3M10 17V19M19 10H17M3 10H1M16.657 16.657L15.243 15.243M4.757 4.757L3.343 3.343M16.657 3.343L15.243 4.757M4.757 15.243L3.343 16.657" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>

View File

@@ -2,11 +2,15 @@
/** /**
* Verdict System Post Meta Registration & Meta Box * Verdict System Post Meta Registration & Meta Box
* *
* Registers post meta for pr_peptide CPT: * Registers post meta for peptide CPT:
* - verdict_state (required): one of 5 states * - verdict_state (required): one of 5 states
* - signal_row_1, signal_row_2, signal_row_3 (optional): evidence signal labels * - verdict_text (required for launch): 3–5 sentence opinionated verdict
* - signal_row_1/2/3 (optional): evidence signal label text
* - signal_row_1/2/3_glyph (optional): per-row glyph; enum ✓|!|?|⊘|⚠
* - has_partner_link (bool, default false): controls inline disclosure render
* *
* Adds a meta box to the pr_peptide edit screen for managing these fields. * Who calls it: loaded by functions.php via require_once.
* Depends on: nothing — pure WP register_post_meta.
* *
* @package peptide-starter * @package peptide-starter
*/ */
@@ -16,21 +20,21 @@ if ( ! defined( 'ABSPATH' ) ) {
} }
/** /**
* Register verdict post meta for pr_peptide CPT. * Register verdict post meta for peptide CPT.
* *
* @return void * @return void
*/ */
function peptide_starter_register_verdict_meta() { function peptide_starter_register_verdict_meta() {
$allowed_states = array( 'established', 'promising', 'investigational', 'insufficient', 'cautionary' ); $allowed_states = array( 'established', 'promising', 'investigational', 'insufficient', 'cautionary' );
$allowed_glyphs = array( '✓', '!', '?', '⊘', '⚠' );
// Register verdict_state meta. // Register verdict_state meta.
register_post_meta( register_post_meta(
'pr_peptide', 'peptide',
'verdict_state', 'verdict_state',
array( array(
'type' => 'string', 'type' => 'string',
'description' => 'Verdict classification state', 'single' => true,
'single' => true,
'show_in_rest' => true, 'show_in_rest' => true,
'auth_callback' => '__return_true', 'auth_callback' => '__return_true',
'sanitize_callback' => function ( $value ) use ( $allowed_states ) { 'sanitize_callback' => function ( $value ) use ( $allowed_states ) {
@@ -39,26 +43,68 @@ function peptide_starter_register_verdict_meta() {
) )
); );
// Register signal row meta (up to 3). // Register verdict_text meta — 3–5 sentence opinionated verdict paragraph.
register_post_meta(
'peptide',
'verdict_text',
array(
'type' => 'string',
'single' => true,
'show_in_rest' => true,
'auth_callback' => '__return_true',
'sanitize_callback' => 'sanitize_textarea_field',
)
);
// Register signal row text meta (up to 3 rows).
for ( $i = 1; $i <= 3; $i++ ) { for ( $i = 1; $i <= 3; $i++ ) {
register_post_meta( register_post_meta(
'pr_peptide', 'peptide',
'signal_row_' . $i, 'signal_row_' . $i,
array( array(
'type' => 'string', 'type' => 'string',
'description' => 'Evidence signal row ' . $i, 'single' => true,
'single' => true,
'show_in_rest' => true, 'show_in_rest' => true,
'auth_callback' => '__return_true', 'auth_callback' => '__return_true',
'sanitize_callback' => 'sanitize_text_field', 'sanitize_callback' => 'sanitize_text_field',
) )
); );
// Register per-row glyph meta.
// Validated as one of the 5-state glyph set to keep visual vocabulary consistent.
register_post_meta(
'peptide',
'signal_row_' . $i . '_glyph',
array(
'type' => 'string',
'single' => true,
'show_in_rest' => true,
'auth_callback' => '__return_true',
'sanitize_callback' => function ( $value ) use ( $allowed_glyphs ) {
return ( '' === $value || in_array( $value, $allowed_glyphs, true ) ) ? $value : '';
},
)
);
} }
// Register has_partner_link — gates inline affiliate disclosure rendering.
// Default false: disclosure is suppressed unless a partner link is explicitly present.
register_post_meta(
'peptide',
'has_partner_link',
array(
'type' => 'boolean',
'single' => true,
'show_in_rest' => true,
'auth_callback' => '__return_true',
'default' => false,
)
);
} }
add_action( 'init', 'peptide_starter_register_verdict_meta' ); add_action( 'init', 'peptide_starter_register_verdict_meta' );
/** /**
* Add verdict meta box to pr_peptide edit screen. * Add verdict meta box to peptide CPT edit screen.
* *
* @return void * @return void
*/ */
@@ -67,7 +113,7 @@ function peptide_starter_add_verdict_meta_box() {
'peptide_starter_verdict', 'peptide_starter_verdict',
esc_html__( 'Verdict System', 'peptide-starter' ), esc_html__( 'Verdict System', 'peptide-starter' ),
'peptide_starter_render_verdict_meta_box', 'peptide_starter_render_verdict_meta_box',
'pr_peptide', 'peptide',
'normal', 'normal',
'high' 'high'
); );
@@ -83,23 +129,24 @@ add_action( 'add_meta_boxes', 'peptide_starter_add_verdict_meta_box' );
function peptide_starter_render_verdict_meta_box( $post ) { function peptide_starter_render_verdict_meta_box( $post ) {
wp_nonce_field( 'peptide_starter_verdict_nonce', 'peptide_starter_verdict_nonce' ); wp_nonce_field( 'peptide_starter_verdict_nonce', 'peptide_starter_verdict_nonce' );
$verdict_state = get_post_meta( $post->ID, 'verdict_state', true ); $verdict_state = get_post_meta( $post->ID, 'verdict_state', true );
$signal_row_1 = get_post_meta( $post->ID, 'signal_row_1', true ); $verdict_text = get_post_meta( $post->ID, 'verdict_text', true );
$signal_row_2 = get_post_meta( $post->ID, 'signal_row_2', true ); $allowed_states = array(
$signal_row_3 = get_post_meta( $post->ID, 'signal_row_3', true ); 'established' => esc_html__( 'Established', 'peptide-starter' ),
$allowed_states = array( 'promising' => esc_html__( 'Promising', 'peptide-starter' ),
'established' => esc_html__( 'Established', 'peptide-starter' ), 'investigational' => esc_html__( 'Investigational', 'peptide-starter' ),
'promising' => esc_html__( 'Promising', 'peptide-starter' ), 'insufficient' => esc_html__( 'Insufficient Evidence', 'peptide-starter' ),
'investigational' => esc_html__( 'Investigational', 'peptide-starter' ), 'cautionary' => esc_html__( 'Cautionary', 'peptide-starter' ),
'insufficient' => esc_html__( 'Insufficient Evidence', 'peptide-starter' ),
'cautionary' => esc_html__( 'Cautionary', 'peptide-starter' ),
); );
$allowed_glyphs = array( '✓', '!', '?', '⊘', '⚠' );
$has_partner = (bool) get_post_meta( $post->ID, 'has_partner_link', true );
?> ?>
<div style="padding: 12px 0;"> <div style="padding:12px 0;">
<label for="verdict_state" style="display: block; margin-bottom: 8px;">
<label for="verdict_state" style="display:block;margin-bottom:6px;">
<strong><?php esc_html_e( 'Verdict State', 'peptide-starter' ); ?></strong> <strong><?php esc_html_e( 'Verdict State', 'peptide-starter' ); ?></strong>
</label> </label>
<select id="verdict_state" name="verdict_state" style="width: 100%; padding: 8px; font-size: 14px;"> <select id="verdict_state" name="verdict_state" style="width:100%;padding:8px;font-size:14px;margin-bottom:16px;">
<option value=""><?php esc_html_e( '— Select —', 'peptide-starter' ); ?></option> <option value=""><?php esc_html_e( '— Select —', 'peptide-starter' ); ?></option>
<?php foreach ( $allowed_states as $key => $label ) : ?> <?php foreach ( $allowed_states as $key => $label ) : ?>
<option value="<?php echo esc_attr( $key ); ?>" <?php selected( $verdict_state, $key ); ?>> <option value="<?php echo esc_attr( $key ); ?>" <?php selected( $verdict_state, $key ); ?>>
@@ -108,22 +155,69 @@ function peptide_starter_render_verdict_meta_box( $post ) {
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
<div style="margin-top: 20px;"> <label for="verdict_text" style="display:block;margin-bottom:6px;">
<label style="display: block; margin-bottom: 4px;"> <strong><?php esc_html_e( 'Our Verdict (3–5 sentences)', 'peptide-starter' ); ?></strong>
<strong><?php esc_html_e( 'Signal Row 1', 'peptide-starter' ); ?></strong> <span style="font-weight:normal;font-size:12px;color:#666;display:block;margin-top:2px;">
</label> <?php esc_html_e( 'The opinionated verdict paragraph shown on the card hero. Be specific — this is the brand\'s editorial spine.', 'peptide-starter' ); ?>
<input type="text" name="signal_row_1" value="<?php echo esc_attr( $signal_row_1 ); ?>" placeholder="<?php esc_attr_e( 'e.g., Strong animal evidence', 'peptide-starter' ); ?>" style="width: 100%; padding: 8px; font-size: 14px; margin-bottom: 12px;"> </span>
</label>
<textarea id="verdict_text" name="verdict_text" rows="5"
style="width:100%;padding:8px;font-size:14px;margin-bottom:16px;"
placeholder="<?php esc_attr_e( 'e.g., Semaglutide works. The data is among the strongest of any peptide therapy...', 'peptide-starter' ); ?>"><?php echo esc_textarea( $verdict_text ); ?></textarea>
<label style="display: block; margin-bottom: 4px;"> <hr style="border:none;border-top:1px solid #ddd;margin:8px 0 16px;">
<strong><?php esc_html_e( 'Signal Row 2', 'peptide-starter' ); ?></strong> <p style="font-weight:bold;margin-bottom:8px;"><?php esc_html_e( 'Evidence Signal Rows', 'peptide-starter' ); ?></p>
</label>
<input type="text" name="signal_row_2" value="<?php echo esc_attr( $signal_row_2 ); ?>" placeholder="<?php esc_attr_e( 'e.g., Limited human data', 'peptide-starter' ); ?>" style="width: 100%; padding: 8px; font-size: 14px; margin-bottom: 12px;"> <?php for ( $i = 1; $i <= 3; $i++ ) :
$row_text = get_post_meta( $post->ID, 'signal_row_' . $i, true );
$row_glyph = get_post_meta( $post->ID, 'signal_row_' . $i . '_glyph', true );
?>
<div style="display:flex;gap:8px;align-items:flex-start;margin-bottom:12px;">
<div style="flex:0 0 80px;">
<label for="signal_row_<?php echo esc_attr( $i ); ?>_glyph"
style="display:block;margin-bottom:4px;font-size:12px;color:#444;">
<?php esc_html_e( 'Glyph', 'peptide-starter' ); ?>
</label>
<select id="signal_row_<?php echo esc_attr( $i ); ?>_glyph"
name="signal_row_<?php echo esc_attr( $i ); ?>_glyph"
style="width:100%;padding:6px;font-size:16px;">
<option value="" <?php selected( $row_glyph, '' ); ?>>—</option>
<?php foreach ( $allowed_glyphs as $g ) : ?>
<option value="<?php echo esc_attr( $g ); ?>" <?php selected( $row_glyph, $g ); ?>>
<?php echo esc_html( $g ); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div style="flex:1;">
<label for="signal_row_<?php echo esc_attr( $i ); ?>"
style="display:block;margin-bottom:4px;font-size:12px;color:#444;">
<?php
/* translators: %d is the row number */
echo esc_html( sprintf( __( 'Row %d label', 'peptide-starter' ), $i ) );
?>
</label>
<input type="text" id="signal_row_<?php echo esc_attr( $i ); ?>"
name="signal_row_<?php echo esc_attr( $i ); ?>"
value="<?php echo esc_attr( $row_text ); ?>"
style="width:100%;padding:8px;font-size:14px;"
placeholder="<?php esc_attr_e( 'Evidence signal label', 'peptide-starter' ); ?>">
</div>
</div>
<?php endfor; ?>
<hr style="border:none;border-top:1px solid #ddd;margin:8px 0 16px;">
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;">
<input type="checkbox" name="has_partner_link" value="1"
<?php checked( $has_partner, true ); ?> style="width:16px;height:16px;">
<span>
<strong><?php esc_html_e( 'Has active partner link', 'peptide-starter' ); ?></strong>
<span style="display:block;font-size:12px;color:#666;margin-top:2px;">
<?php esc_html_e( 'Check this when the monograph contains an affiliate partner link. Controls the inline disclosure strip.', 'peptide-starter' ); ?>
</span>
</span>
</label>
<label style="display: block; margin-bottom: 4px;">
<strong><?php esc_html_e( 'Signal Row 3', 'peptide-starter' ); ?></strong>
</label>
<input type="text" name="signal_row_3" value="<?php echo esc_attr( $signal_row_3 ); ?>" placeholder="<?php esc_attr_e( 'e.g., Regulatory status varies', 'peptide-starter' ); ?>" style="width: 100%; padding: 8px; font-size: 14px;">
</div>
</div> </div>
<?php <?php
} }
@@ -137,21 +231,23 @@ function peptide_starter_render_verdict_meta_box( $post ) {
*/ */
function peptide_starter_save_verdict_meta( $post_id, $post ) { function peptide_starter_save_verdict_meta( $post_id, $post ) {
// Verify nonce. // Verify nonce.
if ( ! isset( $_POST['peptide_starter_verdict_nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['peptide_starter_verdict_nonce'] ), 'peptide_starter_verdict_nonce' ) ) { if ( ! isset( $_POST['peptide_starter_verdict_nonce'] )
|| ! wp_verify_nonce( sanitize_key( $_POST['peptide_starter_verdict_nonce'] ), 'peptide_starter_verdict_nonce' )
) {
return; return;
} }
// Only run for pr_peptide CPT. // Only run for peptide CPT.
if ( 'pr_peptide' !== $post->post_type ) { if ( 'peptide' !== $post->post_type ) {
return; return;
} }
// Allow only editors and admins.
if ( ! current_user_can( 'edit_post', $post_id ) ) { if ( ! current_user_can( 'edit_post', $post_id ) ) {
return; return;
} }
$allowed_states = array( 'established', 'promising', 'investigational', 'insufficient', 'cautionary' ); $allowed_states = array( 'established', 'promising', 'investigational', 'insufficient', 'cautionary' );
$allowed_glyphs = array( '✓', '!', '?', '⊘', '⚠' );
// Save verdict_state. // Save verdict_state.
if ( isset( $_POST['verdict_state'] ) ) { if ( isset( $_POST['verdict_state'] ) ) {
@@ -163,7 +259,17 @@ function peptide_starter_save_verdict_meta( $post_id, $post ) {
} }
} }
// Save signal rows. // Save verdict_text.
if ( isset( $_POST['verdict_text'] ) ) {
$text = sanitize_textarea_field( wp_unslash( $_POST['verdict_text'] ) );
if ( ! empty( $text ) ) {
update_post_meta( $post_id, 'verdict_text', $text );
} else {
delete_post_meta( $post_id, 'verdict_text' );
}
}
// Save signal rows and their per-row glyphs.
for ( $i = 1; $i <= 3; $i++ ) { for ( $i = 1; $i <= 3; $i++ ) {
$field_name = 'signal_row_' . $i; $field_name = 'signal_row_' . $i;
if ( isset( $_POST[ $field_name ] ) ) { if ( isset( $_POST[ $field_name ] ) ) {
@@ -174,6 +280,20 @@ function peptide_starter_save_verdict_meta( $post_id, $post ) {
delete_post_meta( $post_id, $field_name ); delete_post_meta( $post_id, $field_name );
} }
} }
$glyph_field = 'signal_row_' . $i . '_glyph';
if ( isset( $_POST[ $glyph_field ] ) ) {
$glyph = wp_unslash( $_POST[ $glyph_field ] );
if ( in_array( $glyph, $allowed_glyphs, true ) ) {
update_post_meta( $post_id, $glyph_field, $glyph );
} else {
delete_post_meta( $post_id, $glyph_field );
}
}
} }
// Save has_partner_link (checkbox — present = true, absent = false).
$has_partner = isset( $_POST['has_partner_link'] ) && '1' === $_POST['has_partner_link'];
update_post_meta( $post_id, 'has_partner_link', $has_partner ? '1' : '0' );
} }
add_action( 'save_post', 'peptide_starter_save_verdict_meta', 10, 2 ); add_action( 'save_post', 'peptide_starter_save_verdict_meta', 10, 2 );

View File

@@ -42,4 +42,5 @@ get_header();
</div> </div>
</main> </main>
<?php get_footer(); <?php
get_footer();

View File

@@ -41,4 +41,5 @@ get_header();
</div> </div>
</main> </main>
<?php get_footer(); <?php
get_footer();

View File

@@ -2,8 +2,18 @@
/** /**
* The single peptide template * The single peptide template
* *
* Displays a single peptide with back link, content box, * Displays a single peptide with verdict card hero, content, affiliate
* and meta information in a card grid layout. * disclosure, and meta properties in a card grid layout.
*
* When a verdict_state is set on the post, the verdict card owns the <h1>
* title so the page-header section only renders the back-link and category
* pills (no duplicate heading). When no verdict state exists the classic <h1>
* renders in the header for backwards compatibility with non-monograph posts.
*
* Who calls it: WordPress template hierarchy for the pr_peptide CPT.
* Depends on: inc/verdict-meta.php (post meta registration),
* template-parts/verdict/card.php,
* template-parts/affiliate-disclosure.php.
* *
* @package peptide-starter * @package peptide-starter
*/ */
@@ -16,6 +26,9 @@ get_header();
if ( have_posts() ) { if ( have_posts() ) {
while ( have_posts() ) { while ( have_posts() ) {
the_post(); the_post();
// Determine verdict state once — used in two places below.
$verdict_state = get_post_meta( get_the_ID(), 'verdict_state', true );
?> ?>
<!-- Page Header --> <!-- Page Header -->
<header class="page-header"> <header class="page-header">
@@ -28,7 +41,7 @@ get_header();
</a> </a>
<?php <?php
// Show featured badge if the peptide is marked as featured // Show featured badge if the peptide is marked as featured.
if ( is_sticky() || get_post_meta( get_the_ID(), 'peptide_featured', true ) ) { if ( is_sticky() || get_post_meta( get_the_ID(), 'peptide_featured', true ) ) {
?> ?>
<span class="ps-featured-badge">&#9733; <?php esc_html_e( 'Featured', 'peptide-starter' ); ?></span> <span class="ps-featured-badge">&#9733; <?php esc_html_e( 'Featured', 'peptide-starter' ); ?></span>
@@ -36,10 +49,19 @@ get_header();
} }
?> ?>
<h1 class="page-title"><?php the_title(); ?></h1> <?php
// Only render the page-level <h1> when no verdict card is present.
// The verdict card component owns the <h1> for monograph posts so
// that readers encounter a single, semantically correct heading.
if ( empty( $verdict_state ) ) {
?>
<h1 class="page-title"><?php the_title(); ?></h1>
<?php
}
?>
<?php <?php
// Category tag pills // Category tag pills.
$categories = get_the_terms( get_the_ID(), 'peptide-category' ); $categories = get_the_terms( get_the_ID(), 'peptide-category' );
if ( $categories && ! is_wp_error( $categories ) ) { if ( $categories && ! is_wp_error( $categories ) ) {
echo '<div class="peptide-card__meta" style="margin-top: var(--spacing-sm);">'; echo '<div class="peptide-card__meta" style="margin-top: var(--spacing-sm);">';
@@ -52,6 +74,17 @@ get_header();
</div> </div>
</header> </header>
<!-- Verdict Card Hero (monograph posts only) -->
<?php
if ( ! empty( $verdict_state ) ) {
get_template_part(
'template-parts/verdict/card',
null,
array( 'post_id' => get_the_ID() )
);
}
?>
<!-- Single Peptide Content --> <!-- Single Peptide Content -->
<div class="page-content"> <div class="page-content">
<article class="peptide-single" id="post-<?php the_ID(); ?>" <?php post_class(); ?>> <article class="peptide-single" id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
@@ -69,6 +102,22 @@ get_header();
</div> </div>
</div> </div>
<?php
// Inline disclosure renders after the content box (below any partner
// links in the_content). Conditional on has_partner_link meta — if no
// partner link exists on this monograph, the disclosure is suppressed.
// Default false: new monographs are disclosure-off until a partner is
// added and the meta is explicitly set to true by the CMO.
$has_partner_link = (bool) get_post_meta( get_the_ID(), 'has_partner_link', true );
if ( $verdict_state && $has_partner_link ) {
get_template_part(
'template-parts/affiliate-disclosure',
null,
array( 'context' => 'inline' )
);
}
?>
<!-- Meta Information as card grid --> <!-- Meta Information as card grid -->
<?php <?php
$formula = get_post_meta( get_the_ID(), 'peptide_formula', true ); $formula = get_post_meta( get_the_ID(), 'peptide_formula', true );

View File

@@ -4,7 +4,7 @@ Theme URI: https://github.com/peptiderepo/peptide-starter
Description: Premium, accessible WordPress theme for scientific peptide reference database Description: Premium, accessible WordPress theme for scientific peptide reference database
Author: Peptide Repo Author: Peptide Repo
Author URI: https://peptiderepo.com Author URI: https://peptiderepo.com
Version: 2.0.0-alpha.1 Version: 2.0.0-alpha.3
License: GPL v2 or later License: GPL v2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: peptide-starter Text Domain: peptide-starter

View File

@@ -17,11 +17,26 @@ $show_label = isset( $args['show_label'] ) ? (bool) $args['show_label'] : tru
// State configuration. // State configuration.
$states = array( $states = array(
'established' => array( 'glyph' => '✓', 'label' => 'Established' ), 'established' => array(
'promising' => array( 'glyph' => '◐', 'label' => 'Promising' ), 'glyph' => '✓',
'investigational' => array( 'glyph' => '?', 'label' => 'Investigational' ), 'label' => 'Established',
'insufficient' => array( 'glyph' => '⊘', 'label' => 'Insufficient Evidence' ), ),
'cautionary' => array( 'glyph' => '⚠', 'label' => 'Cautionary' ), 'promising' => array(
'glyph' => '◐',
'label' => 'Promising',
),
'investigational' => array(
'glyph' => '?',
'label' => 'Investigational',
),
'insufficient' => array(
'glyph' => '⊘',
'label' => 'Insufficient Evidence',
),
'cautionary' => array(
'glyph' => '⚠',
'label' => 'Cautionary',
),
); );
// Early exit if invalid state. // Early exit if invalid state.

View File

@@ -2,10 +2,17 @@
/** /**
* Verdict Card Component (Monograph Hero) * Verdict Card Component (Monograph Hero)
* *
* Displays a full verdict card for a pr_peptide post. * Displays the full verdict card hero for a peptide post.
* Reads verdict_state and signal_row_1/2/3 from post meta. * Reads verdict_state, verdict_text, signal_row_1/2/3, and
* Accepts $args['post_id']. * signal_row_1/2/3_glyph from post meta.
* *
* Who calls it: single-peptide.php for posts with a verdict_state.
* Depends on: inc/verdict-meta.php (meta registration),
* template-parts/verdict/badge.php,
* template-parts/verdict/evidence-row.php.
*
* @see inc/verdict-meta.php
* @see single-peptide.php
* @package peptide-starter * @package peptide-starter
*/ */
@@ -15,27 +22,32 @@ if ( ! defined( 'ABSPATH' ) ) {
$post_id = isset( $args['post_id'] ) ? absint( $args['post_id'] ) : get_the_ID(); $post_id = isset( $args['post_id'] ) ? absint( $args['post_id'] ) : get_the_ID();
// Get verdict state from post meta. // Get verdict state; bail early if unset.
$verdict_state = get_post_meta( $post_id, 'verdict_state', true ); $verdict_state = get_post_meta( $post_id, 'verdict_state', true );
// Early exit if no verdict state is set.
if ( empty( $verdict_state ) ) { if ( empty( $verdict_state ) ) {
return; return;
} }
// Collect signal rows. // Get opinionated verdict text; fall back to placeholder if not yet authored.
$verdict_text = get_post_meta( $post_id, 'verdict_text', true );
if ( empty( $verdict_text ) ) {
$verdict_text = __( 'This peptide merits careful scientific review. Please consult with a healthcare provider before use.', 'peptide-starter' );
}
// Collect signal rows with their per-row glyphs.
// Glyph comes from the signal_row_N_glyph meta field; if unset, no glyph is
// rendered. This is intentional — a missing glyph is better than a wrong one.
$signals = array(); $signals = array();
for ( $i = 1; $i <= 3; $i++ ) { for ( $i = 1; $i <= 3; $i++ ) {
$signal_text = get_post_meta( $post_id, 'signal_row_' . $i, true ); $signal_text = get_post_meta( $post_id, 'signal_row_' . $i, true );
$signal_glyph = get_post_meta( $post_id, 'signal_row_' . $i . '_glyph', true );
if ( ! empty( $signal_text ) ) { if ( ! empty( $signal_text ) ) {
$signals[] = array( $signals[] = array(
'glyph' => $i === 1 ? '✓' : ( $i === 2 ? '!' : '?' ), 'glyph' => $signal_glyph, // May be empty string; evidence-row handles that.
'label' => $signal_text, 'label' => $signal_text,
); );
} }
} }
$post = get_post( $post_id );
?> ?>
<div class="pr-verdict-card"> <div class="pr-verdict-card">
<div class="pr-verdict-card__header"> <div class="pr-verdict-card__header">
@@ -51,16 +63,13 @@ $post = get_post( $post_id );
); );
?> ?>
</div> </div>
<time class="pr-verdict-card__updated" datetime="<?php echo esc_attr( get_the_date( 'c', $post_id ) ); ?>"> <!-- last_reviewed_date timestamp deferred to v1.1 — WP post date != editorial review date -->
<?php echo esc_html( 'Updated ' . get_the_date( 'M d, Y', $post_id ) ); ?>
</time>
</div> </div>
<h1 class="pr-verdict-card__title"><?php echo esc_html( get_the_title( $post_id ) ); ?></h1> <h1 class="pr-verdict-card__title"><?php echo esc_html( get_the_title( $post_id ) ); ?></h1>
<p class="pr-verdict-card__summary"> <p class="pr-verdict-card__summary">
<?php <?php
// Get post excerpt or first 100 chars of content.
$summary = get_the_excerpt( $post_id ); $summary = get_the_excerpt( $post_id );
if ( empty( $summary ) ) { if ( empty( $summary ) ) {
$content = get_the_content( null, false, $post_id ); $content = get_the_content( null, false, $post_id );
@@ -82,10 +91,7 @@ $post = get_post( $post_id );
<div class="pr-verdict-card__verdict-label"><?php esc_html_e( 'Our verdict:', 'peptide-starter' ); ?></div> <div class="pr-verdict-card__verdict-label"><?php esc_html_e( 'Our verdict:', 'peptide-starter' ); ?></div>
<div class="pr-verdict-card__verdict-text"> <div class="pr-verdict-card__verdict-text">
<?php <?php echo wp_kses_post( nl2br( $verdict_text ) ); ?>
// Placeholder verdict text; can be extended with a custom meta field.
echo esc_html__( 'This peptide merits careful scientific review. Please consult with a healthcare provider before use.', 'peptide-starter' );
?>
</div> </div>
<div class="pr-verdict-card__cta-group"> <div class="pr-verdict-card__cta-group">