feat: add winking expression (v0.1.1)

Buddy now has a fourth mood tone — wink — with one eye closed, an
asymmetric smirk, and rosier cheeks. Renders as a small variant
inside the existing inline-SVG sprite (still zero image files, no
new assets). When overall mood is >= 75, there's a 30% chance on
each page render that the wink replaces the standard happy face —
gives the pet a touch of unpredictable personality.

Why this commit exists: v0.1.0 had three tones (happy / neutral /
sad). Adding wink is the smallest possible demo that the SVG
expression engine is properly extensible — every future mood,
state, accessory or species can land via the same pattern. ~20
lines of PHP, ~2 lines of CSS, no bundle weight, no dependencies.

CHANGES
- inc/sprite.php: wink added to allowed-tones list. Left eye
  renders as a closed-eye arc instead of the open circle. Mouth
  shifts to an asymmetric smirk. Cheek opacity 0.55 → 0.75 for
  extra cheekiness.
- inc/state.php: buddy_mood_label() returns wink ~30% of the time
  when mood >= 75.
- assets/css/buddy.css: new .buddy-widget__mood--wink and
  .buddy-main__mood--wink rules — warm amber pill.
- About-page version-history leads with v0.1.1; v0.1.0 demoted.

VERSION
- buddy.php header 0.1.0 → 0.1.1
- BUDDY_VERSION constant 0.1.0 → 0.1.1

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 10:36:51 +01:00
parent 48e97862a6
commit 8c38d38a3a
6 changed files with 74 additions and 15 deletions
+35
View File
@@ -9,6 +9,41 @@ Format: [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/) — versi
--- ---
## [0.1.1] — 2026-05-25
### Added — Wink expression 😉
Buddy now has a fourth mood tone: **`wink`** — one eye closed, an
asymmetric smirk, and rosier cheeks. Rendered as a small variant
inside the existing inline-SVG sprite (still zero image files, no
new assets). When Buddy's overall mood is ≥ 75, there's a ~30%
chance on each page render that the wink replaces the standard
happy face. Refresh a few times when Buddy is content and you'll
catch it.
Why this commit exists: the v0.1.0 sprite had three tones
(happy / neutral / sad). Adding `wink` was the smallest possible
demo that the SVG expression engine is properly extensible — every
future mood/state/species can be added the same way. ~20 lines of
PHP, ~2 lines of CSS, no bundle weight, no dependencies.
### Changed
- **`inc/sprite.php`**: added `wink` to the allowed-tones list.
Left eye renders as a closed-eye curve (a small downward arc)
instead of the open circle. Right eye stays normal. Mouth shifts
to an asymmetric smirk curve. Cheek opacity bumped from 0.55 to
0.75 for extra cheekiness.
- **`inc/state.php`**: `buddy_mood_label()` now has a 30% chance of
returning the wink tone when the overall mood score is ≥ 75.
- **`assets/css/buddy.css`**: new `.buddy-widget__mood--wink` and
`.buddy-main__mood--wink` rules — warm amber pill matching the
cheeky vibe.
- **About page** version-history card leads with v0.1.1; v0.1.0
demoted to the previous entry.
- **Plugin version bumped**: header + `BUDDY_VERSION` constant
0.1.0 → 0.1.1.
---
## [0.1.0] — 2026-05-25 ## [0.1.0] — 2026-05-25
**Buddy is born.** First release of a new standalone WordPress plugin extracted-and-rebuilt from the tamagotchi feature that once lived inside A-WP-Notes v1.1.5 (now gracefully retired). Buddy stands on its own as a focused, charming companion plugin for the WordPress dashboard. **Buddy is born.** First release of a new standalone WordPress plugin extracted-and-rebuilt from the tamagotchi feature that once lived inside A-WP-Notes v1.1.5 (now gracefully retired). Buddy stands on its own as a focused, charming companion plugin for the WordPress dashboard.
+2
View File
@@ -57,6 +57,7 @@
vertical-align: middle; vertical-align: middle;
} }
.buddy-widget__mood--happy { background:#e8f5ea; color:#1b6f2d; } .buddy-widget__mood--happy { background:#e8f5ea; color:#1b6f2d; }
.buddy-widget__mood--wink { background:#fef3c7; color:#92400e; }
.buddy-widget__mood--neutral { background:#f0f0f1; color:#3c434a; } .buddy-widget__mood--neutral { background:#f0f0f1; color:#3c434a; }
.buddy-widget__mood--sad { background:#fcf0f1; color:#8a2424; } .buddy-widget__mood--sad { background:#fcf0f1; color:#8a2424; }
@@ -138,6 +139,7 @@
vertical-align: middle; vertical-align: middle;
} }
.buddy-main__mood--happy { background:#e8f5ea; color:#1b6f2d; } .buddy-main__mood--happy { background:#e8f5ea; color:#1b6f2d; }
.buddy-main__mood--wink { background:#fef3c7; color:#92400e; }
.buddy-main__mood--neutral { background:#f0f0f1; color:#3c434a; } .buddy-main__mood--neutral { background:#f0f0f1; color:#3c434a; }
.buddy-main__mood--sad { background:#fcf0f1; color:#8a2424; } .buddy-main__mood--sad { background:#fcf0f1; color:#8a2424; }
+2 -2
View File
@@ -5,7 +5,7 @@
* Plugin Name: Buddy * Plugin Name: Buddy
* Plugin URI: https://icanhelp.ie/buddy * Plugin URI: https://icanhelp.ie/buddy
* Description: Adopt a small companion that lives in your WordPress dashboard. Its mood reflects your site's health — published posts feed it, outdated plugins make it sick, clearing spam makes it happy. Gamifies WordPress maintenance with a bit of charm. * Description: Adopt a small companion that lives in your WordPress dashboard. Its mood reflects your site's health — published posts feed it, outdated plugins make it sick, clearing spam makes it happy. Gamifies WordPress maintenance with a bit of charm.
* Version: 0.1.0 * Version: 0.1.1
* Requires at least: 5.0 * Requires at least: 5.0
* Requires PHP: 7.4 * Requires PHP: 7.4
* Author: David Keane * Author: David Keane
@@ -20,7 +20,7 @@
if ( ! defined( 'ABSPATH' ) ) { exit; } if ( ! defined( 'ABSPATH' ) ) { exit; }
// Plugin coordinates. // Plugin coordinates.
if ( ! defined( 'BUDDY_VERSION' ) ) { define( 'BUDDY_VERSION', '0.1.0' ); } if ( ! defined( 'BUDDY_VERSION' ) ) { define( 'BUDDY_VERSION', '0.1.1' ); }
if ( ! defined( 'BUDDY_FILE' ) ) { define( 'BUDDY_FILE', __FILE__ ); } if ( ! defined( 'BUDDY_FILE' ) ) { define( 'BUDDY_FILE', __FILE__ ); }
if ( ! defined( 'BUDDY_PATH' ) ) { define( 'BUDDY_PATH', plugin_dir_path( __FILE__ ) ); } if ( ! defined( 'BUDDY_PATH' ) ) { define( 'BUDDY_PATH', plugin_dir_path( __FILE__ ) ); }
if ( ! defined( 'BUDDY_URL' ) ) { define( 'BUDDY_URL', plugin_dir_url( __FILE__ ) ); } if ( ! defined( 'BUDDY_URL' ) ) { define( 'BUDDY_URL', plugin_dir_url( __FILE__ ) ); }
+5 -1
View File
@@ -98,7 +98,11 @@ function buddy_render_about_page() {
<h2><?php esc_html_e( 'Version history', 'buddy' ); ?></h2> <h2><?php esc_html_e( 'Version history', 'buddy' ); ?></h2>
<ul> <ul>
<li> <li>
<span class="ver">v0.1.0</span> &mdash; 25 May 2026 <span class="latest">latest</span><br> <span class="ver">v0.1.1</span> &mdash; 25 May 2026 <span class="latest">latest</span><br>
<?php esc_html_e( 'Cheeky face! New wink expression that occasionally appears when Buddy is in a good mood — one eye closed, asymmetric smirk, rosier cheeks. Pure SVG, no image files. Proof that the expression engine is properly extensible.', 'buddy' ); ?>
</li>
<li>
<span class="ver">v0.1.0</span> &mdash; 25 May 2026<br>
<?php esc_html_e( 'First release. Phase A complete: Buddy exists. Dashboard widget + dedicated admin page show the SVG character, name, mood label, four stats bars. Self-hosted update checker wired up to Gitea from commit 1. No interactions yet — that\'s next.', 'buddy' ); ?> <?php esc_html_e( 'First release. Phase A complete: Buddy exists. Dashboard widget + dedicated admin page show the SVG character, name, mood label, four stats bars. Self-hosted update checker wired up to Gitea from commit 1. No interactions yet — that\'s next.', 'buddy' ); ?>
</li> </li>
</ul> </ul>
+19 -10
View File
@@ -23,14 +23,15 @@ if ( ! defined( 'ABSPATH' ) ) { exit; }
*/ */
function buddy_render_sprite( $species = 'default', $tone = 'happy', $size = 'md' ) { function buddy_render_sprite( $species = 'default', $tone = 'happy', $size = 'md' ) {
$species = sanitize_key( $species ); $species = sanitize_key( $species );
$tone = in_array( $tone, array( 'happy', 'neutral', 'sad' ), true ) ? $tone : 'happy'; $tone = in_array( $tone, array( 'happy', 'neutral', 'sad', 'wink' ), true ) ? $tone : 'happy';
$size = in_array( $size, array( 'sm', 'md', 'lg' ), true ) ? $size : 'md'; $size = in_array( $size, array( 'sm', 'md', 'lg' ), true ) ? $size : 'md';
// Mouth path varies by tone. // Mouth path varies by tone.
$mouth = array( $mouth = array(
'happy' => 'M 42 60 Q 50 70 58 60', // smile 'happy' => 'M 42 60 Q 50 70 58 60', // symmetric smile
'neutral' => 'M 42 64 L 58 64', // flat 'neutral' => 'M 42 64 L 58 64', // flat
'sad' => 'M 42 66 Q 50 58 58 66', // frown 'sad' => 'M 42 66 Q 50 58 58 66', // frown (curves up at edges)
'wink' => 'M 42 60 Q 50 70 58 62', // asymmetric smirk — right corner a touch higher
); );
$mouth_d = $mouth[ $tone ]; $mouth_d = $mouth[ $tone ];
@@ -46,10 +47,16 @@ function buddy_render_sprite( $species = 'default', $tone = 'happy', $size = 'md
<circle cx="50" cy="55" r="32" fill="<?php echo esc_attr( $body_fill ); ?>" stroke="#c9941d" stroke-width="2" /> <circle cx="50" cy="55" r="32" fill="<?php echo esc_attr( $body_fill ); ?>" stroke="#c9941d" stroke-width="2" />
<!-- left eye --> <!-- left eye -->
<g class="buddy-sprite__eye buddy-sprite__eye--left"> <g class="buddy-sprite__eye buddy-sprite__eye--left">
<circle cx="40" cy="46" r="5" fill="#2c3338" /> <?php if ( $tone === 'wink' ) : ?>
<circle cx="41.2" cy="45" r="1.5" fill="#fff" /> <!-- Closed left eye for a wink: a downward curved line below the eye-circle's normal y. -->
<path d="M 35 46 Q 40 50 45 46"
stroke="#2c3338" stroke-width="2.4" fill="none" stroke-linecap="round" />
<?php else : ?>
<circle cx="40" cy="46" r="5" fill="#2c3338" />
<circle cx="41.2" cy="45" r="1.5" fill="#fff" />
<?php endif; ?>
</g> </g>
<!-- right eye --> <!-- right eye (always open, even during wink) -->
<g class="buddy-sprite__eye buddy-sprite__eye--right"> <g class="buddy-sprite__eye buddy-sprite__eye--right">
<circle cx="60" cy="46" r="5" fill="#2c3338" /> <circle cx="60" cy="46" r="5" fill="#2c3338" />
<circle cx="61.2" cy="45" r="1.5" fill="#fff" /> <circle cx="61.2" cy="45" r="1.5" fill="#fff" />
@@ -57,10 +64,12 @@ function buddy_render_sprite( $species = 'default', $tone = 'happy', $size = 'md
<!-- mouth --> <!-- mouth -->
<path d="<?php echo esc_attr( $mouth_d ); ?>" <path d="<?php echo esc_attr( $mouth_d ); ?>"
stroke="#2c3338" stroke-width="2" fill="none" stroke-linecap="round" /> stroke="#2c3338" stroke-width="2" fill="none" stroke-linecap="round" />
<!-- cheeks (only when happy or neutral) --> <!-- cheeks: not on the sad face; extra-rosy on the wink (cheeky vibe) -->
<?php if ( $tone !== 'sad' ) : ?> <?php if ( $tone !== 'sad' ) :
<circle cx="33" cy="58" r="3" fill="#f4866a" opacity="0.55" /> $cheek_opacity = ( $tone === 'wink' ) ? '0.75' : '0.55';
<circle cx="67" cy="58" r="3" fill="#f4866a" opacity="0.55" /> ?>
<circle cx="33" cy="58" r="3" fill="#f4866a" opacity="<?php echo esc_attr( $cheek_opacity ); ?>" />
<circle cx="67" cy="58" r="3" fill="#f4866a" opacity="<?php echo esc_attr( $cheek_opacity ); ?>" />
<?php endif; ?> <?php endif; ?>
<!-- tiny feet --> <!-- tiny feet -->
<ellipse cx="42" cy="88" rx="6" ry="3" fill="#c9941d" /> <ellipse cx="42" cy="88" rx="6" ry="3" fill="#c9941d" />
+11 -2
View File
@@ -101,10 +101,19 @@ function buddy_overall_mood( array $state ) {
} }
/** /**
* Pick a one-word emoji / status label based on overall mood. Pure * Pick a one-word status label + sprite tone based on overall mood.
* cosmetic, used in the dashboard widget header. * Pure cosmetic, used in the dashboard widget header and the main
* admin page.
*
* Easter-egg: when Buddy is genuinely happy (mood >= 75) there's a
* ~30% chance per page-render of returning the "Cheeky" wink tone
* instead of the standard happy face. Gives the pet a touch of
* personality — refresh the page enough and you'll catch the wink.
*/ */
function buddy_mood_label( $mood_score ) { function buddy_mood_label( $mood_score ) {
if ( $mood_score >= 75 && mt_rand( 1, 100 ) <= 30 ) {
return array( 'label' => __( 'Cheeky 😉', 'buddy' ), 'tone' => 'wink' );
}
if ( $mood_score >= 80 ) { return array( 'label' => __( 'Thriving', 'buddy' ), 'tone' => 'happy' ); } if ( $mood_score >= 80 ) { return array( 'label' => __( 'Thriving', 'buddy' ), 'tone' => 'happy' ); }
if ( $mood_score >= 60 ) { return array( 'label' => __( 'Content', 'buddy' ), 'tone' => 'happy' ); } if ( $mood_score >= 60 ) { return array( 'label' => __( 'Content', 'buddy' ), 'tone' => 'happy' ); }
if ( $mood_score >= 40 ) { return array( 'label' => __( 'Okay', 'buddy' ), 'tone' => 'neutral' ); } if ( $mood_score >= 40 ) { return array( 'label' => __( 'Okay', 'buddy' ), 'tone' => 'neutral' ); }