diff --git a/src/components/PairCards.css b/src/components/PairCards.css
index d5b82dd..265f636 100644
--- a/src/components/PairCards.css
+++ b/src/components/PairCards.css
@@ -71,12 +71,31 @@
cursor: pointer;
padding: 0;
flex-shrink: 0;
- transition: background 0.15s;
+ transition: background 0.15s, color 0.15s;
+ color: #7A6E55;
-webkit-user-select: none;
user-select: none;
}
.pair-icon-btn:hover { background: #E0DAC8; }
-.pair-icon-btn.active { background: #E0DAC8; }
+.pair-icon-btn.active { background: #C4A85A22; color: #B07840; }
+
+/* Hold-to-translate button */
+.pair-hold-wrap {
+ width: 38px;
+ height: 38px;
+ flex-shrink: 0;
+ cursor: pointer;
+ -webkit-user-select: none;
+ user-select: none;
+ border-radius: 10px;
+ overflow: visible;
+}
+
+/* Ring fill animation — 2 seconds */
+@keyframes holdRing {
+ from { stroke-dashoffset: 100.53; }
+ to { stroke-dashoffset: 0; }
+}
/* ── Image area ── */
.pair-image-wrap {
@@ -278,6 +297,11 @@
background: #5C3D22;
color: #F5EDE0;
}
+.pair-btn-locked {
+ background: #E0DDD5;
+ color: #B0A898;
+ cursor: not-allowed;
+}
.pair-btn-yes {
background: #3D7055;
color: #fff;
diff --git a/src/components/PairSentenceCard.jsx b/src/components/PairSentenceCard.jsx
index 97263fc..efef815 100644
--- a/src/components/PairSentenceCard.jsx
+++ b/src/components/PairSentenceCard.jsx
@@ -93,11 +93,16 @@ function toPlainText(sentence) {
const LANG_LABELS = { sv: 'Svenska', en: 'English', de: 'Deutsch' }
const LANG_TTS = { sv: 'sv-SE', en: 'en-US', de: 'de-DE' }
+// Circumference of r=16 circle ≈ 100.53
+const RING_C = 2 * Math.PI * 16
+
export default function PairSentenceCard({ card, onComplete }) {
const [done, setDone] = useState(false)
const [activeChip, setActiveChip] = useState(null)
const [showTranslation, setShowTranslation] = useState(false)
- const holdTimer = useRef(null)
+ const [holding, setHolding] = useState(false)
+ const [unlocked, setUnlocked] = useState(false)
+ const holdCompleted = useRef(false)
const lang = card.lang || 'de'
const native = lang === 'de' ? 'en' : 'de'
@@ -117,6 +122,7 @@ export default function PairSentenceCard({ card, onComplete }) {
}
function handleConfirm() {
+ if (!unlocked) return
setDone(true)
setActiveChip(null)
triggerConfetti()
@@ -130,14 +136,21 @@ export default function PairSentenceCard({ card, onComplete }) {
utt.lang = LANG_TTS[lang] || 'de-DE'
utt.rate = 0.9
window.speechSynthesis.speak(utt)
+ setUnlocked(true)
}
- function startTranslation() {
- holdTimer.current = setTimeout(() => setShowTranslation(true), 150)
+ function startHold() {
+ holdCompleted.current = false
+ setHolding(true)
+ setShowTranslation(true)
}
- function endTranslation() {
- clearTimeout(holdTimer.current)
- setShowTranslation(false)
+ function endHold() {
+ setHolding(false)
+ if (!holdCompleted.current) setShowTranslation(false)
+ }
+ function onHoldComplete() {
+ holdCompleted.current = true
+ setUnlocked(true)
}
return (
@@ -159,17 +172,7 @@ export default function PairSentenceCard({ card, onComplete }) {
{isObject &&
Satz
@@ -184,7 +187,7 @@ export default function PairSentenceCard({ card, onComplete }) { color: '#7A7060', transition: 'opacity 0.18s', margin: 0, - marginTop: showTranslation ? 0 : '-1.7em', /* overlay effect */ + marginTop: showTranslation ? 0 : '-1.7em', pointerEvents: 'none', }}> {resolveSentence(hint, card.placeholders, null, null)} @@ -193,30 +196,52 @@ export default function PairSentenceCard({ card, onComplete }) {Frage
{resolveSentence(sentence, card.placeholders, handleChipClick, activeChip?.id)}
diff --git a/src/components/PairYesNoCard.jsx b/src/components/PairYesNoCard.jsx
index 7c6a5d1..29a4773 100644
--- a/src/components/PairYesNoCard.jsx
+++ b/src/components/PairYesNoCard.jsx
@@ -124,17 +124,7 @@ export default function PairYesNoCard({ card, onComplete }) {
{isObject &&
Frage
{resolveSentence(sentence, card.placeholders, handleChipClick, activeChip?.id)}