Files
rangerhq-buddy/inc/sprite.php
T
ranger 0675c9f7d8 fix: wink is now a real CSS animation, no longer sticks (v0.1.3)
The v0.1.1 wink was rendered as a static SVG (left eye drawn as a
closed curve). Once the 5% random gate picked the wink tone, the
left eye stayed closed until the next page render — if the
dashboard sat idle, Buddy was stuck mid-wink indefinitely. David's
report: "the eye does not unblink."

v0.1.2's lower probability (30% → 5%) reduced how often you'd see
the stuck state, but didn't fix the underlying issue.

This release makes the wink a real transient animation:

- sprite.php no longer swaps the left eye for a closed path when
  tone is wink — both eyes are always open circles in the SVG
- buddy.css adds @keyframes buddyWink that briefly closes the
  left eye (scaleY 0.1) for ~250ms every 2.5s, applied only when
  the parent has the .buddy-sprite--wink class
- right eye keeps its normal 5s blink — the asymmetry is what
  makes it read as a wink rather than a synchronised blink
- mouth/cheeks/label still differ for wink tone (those are valid
  static state changes); only the eye behaviour moved to animation

Net effect: when the 5% chance fires, Buddy now actually winks
(closes, opens, closes, opens) instead of freezing one-eye-shut.
2026-05-26 08:08:56 +01:00

80 lines
3.7 KiB
PHP

<?php
/**
* Buddy sprite rendering — pure inline SVG, no image files, no GIFs,
* no sprite sheets. CSS animations live alongside in buddy.css.
*
* Phase A: one species ("default") — a small round yellow chibi
* character with eyes and a smile. Phase D will branch this into
* dog / dragon / sprite / etc.
*
* Mood tone changes the expression: 'happy' = open smile, 'neutral'
* = flat mouth, 'sad' = downturned, 'wink' = asymmetric smirk + rosier
* cheeks + CSS-driven left-eye wink animation. Eyes blink via CSS
* keyframes regardless of tone (it's always alive); the wink keyframe
* lives in buddy.css alongside the regular blink.
*/
if ( ! defined( 'ABSPATH' ) ) { exit; }
/**
* Echo the inline SVG for a buddy character.
*
* @param string $species Currently only 'default'.
* @param string $tone 'happy' | 'neutral' | 'sad'
* @param string $size 'sm' (64px), 'md' (96px), 'lg' (160px). Just sets a class.
*/
function buddy_render_sprite( $species = 'default', $tone = 'happy', $size = 'md' ) {
$species = sanitize_key( $species );
$tone = in_array( $tone, array( 'happy', 'neutral', 'sad', 'wink' ), true ) ? $tone : 'happy';
$size = in_array( $size, array( 'sm', 'md', 'lg' ), true ) ? $size : 'md';
// Mouth path varies by tone.
$mouth = array(
'happy' => 'M 42 60 Q 50 70 58 60', // symmetric smile
'neutral' => 'M 42 64 L 58 64', // flat
'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 ];
// Body fill: subtle shift by tone.
$body_fill = ( $tone === 'sad' ) ? '#d8b04a' : '#f4c64e';
?>
<svg class="buddy-sprite buddy-sprite--<?php echo esc_attr( $size ); ?> buddy-sprite--<?php echo esc_attr( $tone ); ?>"
viewBox="0 0 100 100"
xmlns="http://www.w3.org/2000/svg"
role="img"
aria-label="<?php echo esc_attr( sprintf( __( 'Buddy is %s', 'buddy' ), $tone ) ); ?>">
<!-- body -->
<circle cx="50" cy="55" r="32" fill="<?php echo esc_attr( $body_fill ); ?>" stroke="#c9941d" stroke-width="2" />
<!-- left eye — always an open circle in the SVG. When the
tone is 'wink', the buddy-sprite--wink class on the parent
triggers a CSS keyframe animation (buddyWink in buddy.css)
that briefly closes this eye every couple of seconds.
That way the wink is a transient action, not a stuck state. -->
<g class="buddy-sprite__eye buddy-sprite__eye--left">
<circle cx="40" cy="46" r="5" fill="#2c3338" />
<circle cx="41.2" cy="45" r="1.5" fill="#fff" />
</g>
<!-- right eye — always open, normal slow blink even during wink -->
<g class="buddy-sprite__eye buddy-sprite__eye--right">
<circle cx="60" cy="46" r="5" fill="#2c3338" />
<circle cx="61.2" cy="45" r="1.5" fill="#fff" />
</g>
<!-- mouth -->
<path d="<?php echo esc_attr( $mouth_d ); ?>"
stroke="#2c3338" stroke-width="2" fill="none" stroke-linecap="round" />
<!-- cheeks: not on the sad face; extra-rosy on the wink (cheeky vibe) -->
<?php if ( $tone !== 'sad' ) :
$cheek_opacity = ( $tone === 'wink' ) ? '0.75' : '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; ?>
<!-- tiny feet -->
<ellipse cx="42" cy="88" rx="6" ry="3" fill="#c9941d" />
<ellipse cx="58" cy="88" rx="6" ry="3" fill="#c9941d" />
</svg>
<?php
}