diff --git a/tests/test_button_effects.sh b/tests/test_button_effects.sh new file mode 100755 index 00000000..d7b2ee49 --- /dev/null +++ b/tests/test_button_effects.sh @@ -0,0 +1,142 @@ +#!/bin/bash +# Test button tactile interaction effects +# Verifies CSS interaction properties exist in VSCode webview stylesheets + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +PLAN_CSS="$PROJECT_ROOT/vscode/webview/plan/styles.css" +SETTINGS_CSS="$PROJECT_ROOT/vscode/webview/settings/styles.css" + +ERRORS=0 + +echo "=== Testing Button Tactile Effects ===" +echo "" + +# Test 1: Base button hover state +echo -n "Test 1: Base button has :hover state... " +if grep -q "button:hover" "$PLAN_CSS"; then + echo "PASS" +else + echo "FAIL" + ERRORS=$((ERRORS + 1)) +fi + +# Test 2: Base button active state +echo -n "Test 2: Base button has :active state... " +if grep -q "button:active" "$PLAN_CSS"; then + echo "PASS" +else + echo "FAIL" + ERRORS=$((ERRORS + 1)) +fi + +# Test 3: Base button transition property +echo -n "Test 3: Base button has transition property... " +if grep -A12 "^button {" "$PLAN_CSS" | grep -q "transition:"; then + echo "PASS" +else + echo "FAIL" + ERRORS=$((ERRORS + 1)) +fi + +# Test 4: Widget button variants have hover state +echo -n "Test 4: Widget buttons have :hover state... " +if grep -q "\.widget-button:hover" "$PLAN_CSS"; then + echo "PASS" +else + echo "FAIL" + ERRORS=$((ERRORS + 1)) +fi + +# Test 5: Widget button variants have active state +echo -n "Test 5: Widget buttons have :active state... " +if grep -q "\.widget-button:active" "$PLAN_CSS"; then + echo "PASS" +else + echo "FAIL" + ERRORS=$((ERRORS + 1)) +fi + +# Test 6: Icon buttons (toggle) have hover state +echo -n "Test 6: Toggle buttons have :hover state... " +if grep -q "\.toggle:hover" "$PLAN_CSS"; then + echo "PASS" +else + echo "FAIL" + ERRORS=$((ERRORS + 1)) +fi + +# Test 7: Icon buttons (toggle) have active state +echo -n "Test 7: Toggle buttons have :active state... " +if grep -q "\.toggle:active" "$PLAN_CSS"; then + echo "PASS" +else + echo "FAIL" + ERRORS=$((ERRORS + 1)) +fi + +# Test 8: Terminal toggle has hover state +echo -n "Test 8: Terminal toggle has :hover state... " +if grep -q "\.terminal-toggle:hover" "$PLAN_CSS"; then + echo "PASS" +else + echo "FAIL" + ERRORS=$((ERRORS + 1)) +fi + +# Test 9: Terminal toggle has active state +echo -n "Test 9: Terminal toggle has :active state... " +if grep -q "\.terminal-toggle:active" "$PLAN_CSS"; then + echo "PASS" +else + echo "FAIL" + ERRORS=$((ERRORS + 1)) +fi + +# Test 10: Settings link has active state +echo -n "Test 10: Settings link has :active state... " +if grep -q "\.settings-link:active" "$SETTINGS_CSS"; then + echo "PASS" +else + echo "FAIL" + ERRORS=$((ERRORS + 1)) +fi + +# Test 11: prefers-reduced-motion media query in plan/styles.css +echo -n "Test 11: prefers-reduced-motion in plan/styles.css... " +if grep -q "prefers-reduced-motion" "$PLAN_CSS"; then + echo "PASS" +else + echo "FAIL" + ERRORS=$((ERRORS + 1)) +fi + +# Test 12: prefers-reduced-motion media query in settings/styles.css +echo -n "Test 12: prefers-reduced-motion in settings/styles.css... " +if grep -q "prefers-reduced-motion" "$SETTINGS_CSS"; then + echo "PASS" +else + echo "FAIL" + ERRORS=$((ERRORS + 1)) +fi + +# Test 13: 120ms transition timing +echo -n "Test 13: Transition timing is 120ms... " +if grep -q "120ms" "$PLAN_CSS"; then + echo "PASS" +else + echo "FAIL" + ERRORS=$((ERRORS + 1)) +fi + +echo "" +if [ $ERRORS -eq 0 ]; then + echo "=== All tests passed! ===" + exit 0 +else + echo "=== $ERRORS test(s) failed ===" + exit 1 +fi diff --git a/vscode/webview/plan/styles.css b/vscode/webview/plan/styles.css index 520ba651..3b5fae88 100644 --- a/vscode/webview/plan/styles.css +++ b/vscode/webview/plan/styles.css @@ -78,6 +78,18 @@ button { color: var(--text); padding: 6px 10px; cursor: pointer; + /* Tactile feedback: smooth transitions for hover/active states */ + transition: transform 120ms ease-out, box-shadow 120ms ease-out, background-color 120ms ease-out; +} + +button:hover { + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); +} + +button:active { + transform: translateY(1px); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); } button.primary { @@ -187,6 +199,18 @@ button:disabled { font-weight: 700; color: var(--accent); padding: 0 6px; + /* Borderless buttons use opacity/scale instead of shadow for tactile feedback */ + transition: opacity 120ms ease-out, transform 120ms ease-out; +} + +.toggle:hover { + opacity: 0.8; + transform: scale(1.05); +} + +.toggle:active { + opacity: 0.6; + transform: scale(0.95); } .delete { @@ -262,6 +286,18 @@ button:disabled { padding: 0 4px; cursor: pointer; font-size: 12px; + /* Borderless buttons use opacity/scale instead of shadow for tactile feedback */ + transition: opacity 120ms ease-out, transform 120ms ease-out; +} + +.terminal-toggle:hover { + opacity: 0.8; + transform: scale(1.05); +} + +.terminal-toggle:active { + opacity: 0.6; + transform: scale(0.95); } .terminal-title { @@ -324,24 +360,72 @@ button:disabled { /* Equal-width buttons: flex distributes space evenly, min-width prevents truncation. */ flex: 1 1 0; min-width: 70px; + /* Tactile feedback: smooth transitions for hover/active states */ + transition: transform 120ms ease-out, box-shadow 120ms ease-out, background-color 120ms ease-out; +} + +.widget-button:hover { + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); +} + +.widget-button:active { + transform: translateY(1px); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); } .widget-button-primary { border-color: var(--accent); background: var(--accent); color: #ffffff; + /* Tactile feedback: smooth transitions for hover/active states */ + transition: transform 120ms ease-out, box-shadow 120ms ease-out, background-color 120ms ease-out; +} + +.widget-button-primary:hover { + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); +} + +.widget-button-primary:active { + transform: translateY(1px); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); } .widget-button-danger { border-color: var(--error); color: var(--error); background: rgba(156, 42, 42, 0.08); + /* Tactile feedback: smooth transitions for hover/active states */ + transition: transform 120ms ease-out, box-shadow 120ms ease-out, background-color 120ms ease-out; +} + +.widget-button-danger:hover { + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); +} + +.widget-button-danger:active { + transform: translateY(1px); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); } .widget-button-ghost { border-color: var(--border); color: var(--accent); background: rgba(15, 110, 110, 0.08); + /* Tactile feedback: smooth transitions for hover/active states */ + transition: transform 120ms ease-out, box-shadow 120ms ease-out, background-color 120ms ease-out; +} + +.widget-button-ghost:hover { + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); +} + +.widget-button-ghost:active { + transform: translateY(1px); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); } .widget-input { @@ -492,3 +576,12 @@ button:disabled { flex-wrap: wrap; } } + +/* Accessibility: disable animations for users who prefer reduced motion */ +@media (prefers-reduced-motion: reduce) { + button, .widget-button, .widget-button-primary, .widget-button-danger, + .widget-button-ghost, .toggle, .terminal-toggle { + transition: none; + transform: none; + } +} diff --git a/vscode/webview/settings/styles.css b/vscode/webview/settings/styles.css index 294641a4..e41fe6d4 100644 --- a/vscode/webview/settings/styles.css +++ b/vscode/webview/settings/styles.css @@ -79,12 +79,20 @@ text-decoration: none; transition: border-color 120ms ease-out, - background 120ms ease-out; + background 120ms ease-out, + color 150ms ease, + transform 120ms ease-out; } .settings-link:hover { border-color: rgba(15, 110, 110, 0.42); background: rgba(15, 110, 110, 0.08); + color: var(--accent); + transform: translateX(2px); +} + +.settings-link:active { + transform: translateX(4px); } .settings-link:focus-visible { @@ -103,3 +111,11 @@ padding: 18px 14px; } } + +/* Accessibility: disable animations for users who prefer reduced motion */ +@media (prefers-reduced-motion: reduce) { + .settings-link { + transition: none; + transform: none; + } +}