0 },
get firstVideoIndex() { return this.hasVideos ? this.images.length : -1 },
get total() { return this.images.length + this.productVideos.length },
modalOpen: false,
modalActive: 0,
// Drag/swipe state
isDragging: false,
wasDragging: false,
startX: 0,
currentX: 0,
dragOffset: 0,
containerWidth: 0,
startY: 0,
directionLocked: null,
isRtl: false,
imageLoading: false,
// Zoom state
zoomScale: 1,
zoomPanX: 0,
zoomPanY: 0,
zoomPinching: false,
zoomPanning: false,
zoomInitialDistance: 0,
zoomInitialScale: 1,
zoomPanStartX: 0,
zoomPanStartY: 0,
zoomPanOffsetX: 0,
zoomPanOffsetY: 0,
zoomLastTap: 0,
zoomLastTouchX: 0,
zoomLastTouchY: 0,
zoomAnimating: false,
lastInputWasTouch: false,
init() {
this.isRtl = document.documentElement.dir === 'rtl';
this.$nextTick(() => {
this.containerWidth = this.$refs.carouselContainer?.offsetWidth || 0;
// Handle already-cached images where x-on:load won't fire
this.$el.querySelectorAll('img.lqip-placeholder').forEach(lqip => {
const realImg = lqip.nextElementSibling;
if (realImg && realImg.complete && realImg.naturalWidth > 0) {
realImg.dataset.lqipDone = '1';
lqip.style.display = 'none';
}
});
});
window.addEventListener('resize', () => {
this.containerWidth = this.$refs.carouselContainer?.offsetWidth || 0;
});
// Preload first image of each variant for instant switching
Object.values(this.variationImagesMap).forEach(variant => {
if (variant.images && variant.images[0]) { new Image().src = variant.images[0]; }
});
// Only the active slide may play. Clearing startedVideo also
// drops the bound src, so swiping away stops the download and
// not just the playback.
this.$watch('active', () => {
this.startedVideo = null;
this.$el.querySelectorAll('video[data-product-video]').forEach(v => v.pause());
});
},
handleDragStart(e) {
if (this.total <= 1) return;
this.isDragging = true;
this.directionLocked = null;
this.startX = e.type.includes('mouse') ? e.clientX : e.touches[0].clientX;
this.startY = e.type.includes('mouse') ? e.clientY : e.touches[0].clientY;
this.currentX = this.startX;
this.containerWidth = this.$refs.carouselContainer?.offsetWidth || 0;
},
handleDragMove(e) {
if (!this.isDragging) return;
const clientX = e.type.includes('mouse') ? e.clientX : e.touches[0].clientX;
const clientY = e.type.includes('mouse') ? e.clientY : e.touches[0].clientY;
if (this.directionLocked === null) {
const deltaX = Math.abs(clientX - this.startX);
const deltaY = Math.abs(clientY - this.startY);
if (deltaX < 5 && deltaY < 5) return;
if (deltaY > deltaX) {
this.directionLocked = 'vertical';
this.isDragging = false;
this.dragOffset = 0;
return;
}
this.directionLocked = 'horizontal';
}
if (this.directionLocked === 'vertical') return;
e.preventDefault();
this.currentX = clientX;
let diff = this.currentX - this.startX;
// Limit drag at edges
const atFirst = this.active === 0;
const atLast = this.active === this.total - 1;
const isAtEdge = this.isRtl ?
(atFirst && diff < 0) || (atLast && diff > 0) :
(atFirst && diff > 0) || (atLast && diff < 0);
if (isAtEdge) {
diff = diff * 0.3; // Resistance at edges
}
this.dragOffset = diff;
},
handleDragEnd() {
this.directionLocked = null;
if (!this.isDragging) return;
this.isDragging = false;
// Mark that we just finished dragging to prevent click from opening modal
if (Math.abs(this.dragOffset) > 5) {
this.wasDragging = true;
setTimeout(() => { this.wasDragging = false; }, 100);
}
const threshold = this.containerWidth * 0.2;
if (Math.abs(this.dragOffset) > threshold) {
if (this.isRtl) {
// RTL: drag left (negative) = prev, drag right (positive) = next
if (this.dragOffset < 0) {
this.prev();
} else {
this.next();
}
} else {
// LTR: drag right (positive) = prev, drag left (negative) = next
if (this.dragOffset > 0) {
this.prev();
} else {
this.next();
}
}
}
this.dragOffset = 0;
},
getSlideTransform() {
// RTL: images flow right-to-left, so we use positive offset
// LTR: images flow left-to-right, so we use negative offset
const baseOffset = this.isRtl ? this.active * 100 : -this.active * 100;
const dragPercent = this.containerWidth > 0 ? (this.dragOffset / this.containerWidth) * 100 : 0;
return `translateX(${baseOffset + dragPercent}%)`;
},
next() {
if (this.active < this.total - 1) {
this.active = this.active + 1;
}
},
prev() {
if (this.active > 0) {
this.active = this.active - 1;
}
},
goTo(idx) { this.active = idx },
openModal(idx) {
if (this.isDragging || this.wasDragging) return;
// The lightbox is images only. Its surface is touch-action:none
// with modalDragStart calling preventDefault() on every touch,
// which would swallow the native video control bar - and
// native fullscreen is one tap away from the inline player
// anyway. Video slides simply never open it.
if (this.hasVideos && idx >= this.firstVideoIndex) return;
this.modalActive = idx;
this.modalOpen = true;
document.body.style.overflow = 'hidden';
},
closeModal() {
this.resetZoom();
this.modalOpen = false;
document.body.style.overflow = 'auto';
},
// Wrap on the image count, not `total`: the modal never shows a
// video, so paging must not land on a video index.
get modalTotal() { return Math.max(1, this.images.length) },
modalNext() { this.resetZoom(); this.modalActive = (this.modalActive + 1) % this.modalTotal },
modalPrev() { this.resetZoom(); this.modalActive = (this.modalActive - 1 + this.modalTotal) % this.modalTotal },
modalGoTo(idx) { this.resetZoom(); this.modalActive = idx },
// Modal drag/swipe
modalDragging: false,
modalWasDragging: false,
modalStartX: 0,
modalDragOffset: 0,
// Zoom helpers
resetZoom() {
this.zoomScale = 1;
this.zoomPanX = 0;
this.zoomPanY = 0;
this.zoomPinching = false;
this.zoomPanning = false;
},
getTouchDistance(touches) {
return Math.hypot(
touches[0].clientX - touches[1].clientX,
touches[0].clientY - touches[1].clientY
);
},
clampPan() {
const maxPanX = Math.max(0, (this.zoomScale - 1) * window.innerWidth / 2);
const maxPanY = Math.max(0, (this.zoomScale - 1) * window.innerHeight / 2);
this.zoomPanX = Math.max(-maxPanX, Math.min(maxPanX, this.zoomPanX));
this.zoomPanY = Math.max(-maxPanY, Math.min(maxPanY, this.zoomPanY));
},
modalDragStart(e) {
const isTouch = e.type.includes('touch');
this.lastInputWasTouch = isTouch;
// Store touch position for double-tap zoom center
if (isTouch && e.touches[0]) {
this.zoomLastTouchX = e.touches[0].clientX;
this.zoomLastTouchY = e.touches[0].clientY;
}
// Two fingers: start pinch-to-zoom
if (isTouch && e.touches.length === 2) {
this.zoomPinching = true;
this.zoomInitialDistance = this.getTouchDistance(e.touches);
this.zoomInitialScale = this.zoomScale;
this.modalDragging = false;
this.zoomPanning = false;
e.preventDefault();
return;
}
// Single finger/mouse while zoomed: start panning
if (this.zoomScale > 1) {
this.zoomPanning = true;
this.zoomPanStartX = isTouch ? e.touches[0].clientX : e.clientX;
this.zoomPanStartY = isTouch ? e.touches[0].clientY : e.clientY;
this.zoomPanOffsetX = this.zoomPanX;
this.zoomPanOffsetY = this.zoomPanY;
e.preventDefault();
return;
}
// Single finger not zoomed or mouse: existing swipe
// Prevent browser double-tap-to-zoom so our custom handler works
if (isTouch) {
e.preventDefault();
}
if (this.total <= 1) return;
this.modalDragging = true;
this.modalStartX = isTouch ? e.touches[0].clientX : e.clientX;
this.modalDragOffset = 0;
},
modalDragMove(e) {
const isTouch = e.type.includes('touch');
// Pinch-to-zoom
if (this.zoomPinching && isTouch && e.touches.length >= 2) {
e.preventDefault();
const newDist = this.getTouchDistance(e.touches);
let newScale = this.zoomInitialScale * (newDist / this.zoomInitialDistance);
this.zoomScale = Math.max(1, Math.min(4, newScale));
this.clampPan();
return;
}
// Panning while zoomed
if (this.zoomPanning) {
e.preventDefault();
const px = isTouch ? e.touches[0].clientX : e.clientX;
const py = isTouch ? e.touches[0].clientY : e.clientY;
const dx = px - this.zoomPanStartX;
const dy = py - this.zoomPanStartY;
this.zoomPanX = this.zoomPanOffsetX + dx;
this.zoomPanY = this.zoomPanOffsetY + dy;
this.clampPan();
return;
}
// Existing swipe logic
if (!this.modalDragging) return;
e.preventDefault();
const x = isTouch ? e.touches[0].clientX : e.clientX;
this.modalDragOffset = x - this.modalStartX;
},
modalDragEnd(e) {
const isTouch = e && e.type && e.type.includes('touch');
// End pinch
if (this.zoomPinching) {
this.zoomPinching = false;
// Snap to 1 if nearly unzoomed
if (this.zoomScale < 1.1) {
this.resetZoom();
}
this.modalWasDragging = true;
setTimeout(() => { this.modalWasDragging = false; }, 100);
// If one finger remains after pinch, start panning
if (isTouch && e.touches && e.touches.length === 1 && this.zoomScale > 1) {
this.zoomPanning = true;
this.zoomPanStartX = e.touches[0].clientX;
this.zoomPanStartY = e.touches[0].clientY;
this.zoomPanOffsetX = this.zoomPanX;
this.zoomPanOffsetY = this.zoomPanY;
}
return;
}
// End pan
if (this.zoomPanning) {
this.zoomPanning = false;
this.modalWasDragging = true;
setTimeout(() => { this.modalWasDragging = false; }, 100);
// Double-tap to zoom out while panned
if (isTouch) {
const now = Date.now();
if (now - this.zoomLastTap < 300) {
this.zoomAnimating = true;
setTimeout(() => { this.zoomAnimating = false; }, 300);
this.resetZoom();
this.zoomLastTap = 0;
} else {
this.zoomLastTap = now;
}
}
return;
}
// Existing swipe end logic
const wasDragging = this.modalDragging;
if (this.modalDragging) {
this.modalDragging = false;
const absDrag = Math.abs(this.modalDragOffset);
if (absDrag > 5) {
this.modalWasDragging = true;
setTimeout(() => { this.modalWasDragging = false; }, 100);
}
const threshold = 80;
if (absDrag > threshold) {
if (this.isRtl) {
this.modalDragOffset < 0 ? this.modalPrev() : this.modalNext();
} else {
this.modalDragOffset > 0 ? this.modalPrev() : this.modalNext();
}
this.modalDragOffset = 0;
this.zoomLastTap = 0;
return;
}
this.modalDragOffset = 0;
}
// Double-tap detection (works for both zoomed and not zoomed)
if (isTouch) {
const now = Date.now();
if (now - this.zoomLastTap < 300) {
this.zoomAnimating = true;
setTimeout(() => { this.zoomAnimating = false; }, 300);
this.modalWasDragging = true;
setTimeout(() => { this.modalWasDragging = false; }, 200);
if (this.zoomScale > 1) {
this.resetZoom();
} else {
const cx = this.zoomLastTouchX - window.innerWidth / 2;
const cy = this.zoomLastTouchY - window.innerHeight / 2;
this.zoomScale = 2.5;
this.zoomPanX = -cx * (this.zoomScale - 1) / this.zoomScale;
this.zoomPanY = -cy * (this.zoomScale - 1) / this.zoomScale;
this.clampPan();
}
this.zoomLastTap = 0;
return;
}
this.zoomLastTap = now;
// Single tap on backdrop: close modal (touch equivalent of @click.self)
if (e.target === e.currentTarget) {
this.closeModal();
}
}
},
switchToVariation(attrValueId) {
const varImages = this.variationImagesMap[attrValueId];
let newImages, newLargeImages, newLqipImages;
if (varImages && varImages.images && varImages.images.length > 0) {
newImages = varImages.images;
newLargeImages = varImages.largeImages || varImages.images;
newLqipImages = varImages.lqipImages || [];
} else {
newImages = this.defaultImages;
newLargeImages = this.defaultLargeImages;
newLqipImages = this.defaultLqipImages;
}
// Show loading state while first image loads
this.imageLoading = true;
const firstImg = new Image();
firstImg.onload = () => {
this.localImages = newImages;
this.localLargeImages = newLargeImages;
this.localLqipImages = newLqipImages;
this.active = 0;
this.modalActive = 0;
this.imageLoading = false;
};
firstImg.onerror = () => {
this.localImages = newImages;
this.localLargeImages = newLargeImages;
this.localLqipImages = newLqipImages;
this.active = 0;
this.modalActive = 0;
this.imageLoading = false;
};
firstImg.src = newImages[0];
// If already cached, onload fires synchronously or near-instantly
}
}" x-on:keydown.escape.window="closeModal()"
x-on:keydown.arrow-left.window="modalOpen && modalPrev()"
x-on:keydown.arrow-right.window="modalOpen && modalNext()"
x-on:switch-variation-images.window="switchToVariation($event.detail.attrValueId ?? $event.detail[0]?.attrValueId)">
/
0 ? -0.25 : 0.25;
const oldScale = zoomScale;
zoomScale = Math.max(1, Math.min(4, zoomScale + delta));
if (oldScale === 1 && zoomScale > 1) {
const r = $el.getBoundingClientRect();
const cx = $event.clientX - (r.left + r.width/2);
const cy = $event.clientY - (r.top + r.height/2);
zoomPanX = -cx * (zoomScale-1)/zoomScale;
zoomPanY = -cy * (zoomScale-1)/zoomScale;
} else if (zoomScale > 1) {
zoomPanX = zoomPanX * (zoomScale / oldScale);
zoomPanY = zoomPanY * (zoomScale / oldScale);
}
if (zoomScale <= 1) { resetZoom(); } else { clampPan(); }
"
class="relative w-full h-full flex items-center justify-center p-4 [touch-action:none]"
:class="zoomScale > 1 ? (zoomPanning ? 'cursor-grabbing' : 'cursor-grab') : (modalTotal > 1 ? (modalDragging ? 'cursor-grabbing' : 'cursor-grab') : '')"
@mousedown="modalDragStart($event)"
@mousemove="modalDragMove($event)"
@mouseup="modalDragEnd($event)"
@mouseleave="modalDragEnd($event)"
@touchstart="modalDragStart($event)"
@touchmove="modalDragMove($event)"
@touchend="modalDragEnd($event)"
:style="modalDragOffset ? `transform: translateX(${modalDragOffset}px); transition: none;` : 'transition: transform 0.2s ease-out;'">
1) { resetZoom(); } else { const r = $el.getBoundingClientRect(); const cx = $event.clientX - (r.left + r.width/2); const cy = $event.clientY - (r.top + r.height/2); zoomScale = 2.5; zoomPanX = -cx * (zoomScale-1)/zoomScale; zoomPanY = -cy * (zoomScale-1)/zoomScale; clampPan(); } setTimeout(() => { zoomAnimating = false; }, 300);"
draggable="false"
:style="modalActive === largeIdx
? 'position:relative;transform: scale(' + zoomScale + ') translate(' + (zoomPanX / zoomScale) + 'px, ' + (zoomPanY / zoomScale) + 'px); transform-origin: center center; will-change: transform; transition: ' + (zoomAnimating ? 'transform 0.3s ease-out' : (zoomScale > 1 ? 'none' : 'transform 0.2s ease-out')) + ';'
: ''" />
"جيانت" (Giant)
ادوية زراعية
AED 100
السماد الورقي المتميز عالي التركيز "جيانت" (Giant) بتركيبته المتطورة تكنولوجياً والمتوازنة والمصممة خصيصاً للتطبيق عبر المجموع الخضري، هو الحل الزراعي المتكامل لمد النباتات بالعناصر الغذائية الأساسية بشكل فوري وسريع الامتصاص. يعمل هذا السماد على رفع الكفاءة الحيوية للنبات، وتحسين جودة الأوراق والثمار، ودعم المحاصيل في مختلف مراحل نموها الحرجة لضمان إنتاجية وفيرة وعالية الجودة.
إليك المواصفات والدليل الإرشادي الكامل للمنتج :
✨ مميزات المنتج
- امتصاص ورقي فائق السرعة: صُمم بتركيبة خاصة تتيح نفاذية وامتصاصاً فورياً وعالياً للعناصر المغذية عبر الثغور الورقية، مما يمنح النبات معالجة سريعة واستجابة فورية لحالات الضعف أو الإجهاد.
- دعم متوازن وشامل للنبات: يمنح النبات توليفة متكاملة من المغذيات التي تساهم في تقوية البناء الضوئي، تنشيط الإنزيمات الحيوية، وزيادة النضارة الخضرية بشكل متجانس.
- تركيبة سائلة متجانسة: سائل نقي ذواب بالكامل في الماء لا يترك أي ترسبات صلبة، مما يجعله آمناً تماماً على أنظمة ومعدات الرش الحديثة ولا يسبب انسداد الفوهات (البشابير).
- رفع كفاءة ومواصفات جودة الثمار: يساعد في زيادة تماسك وحجم وجودة المحصول النهائي، مما يرفع القيمة التسويقية لمنتجاتك الزراعية في الأسواق ويزيد من عوائدك الربحية.
🧪 المواصفات الفنية والتركيب (Specification)
- الاسم التجاري للمنتج: جيانت (Giant).
- نوع المنتج: سماد ورقي سائل متخصص (Folio Nutrient Fertilizer).
- محتوى المغذيات المتوازن: تركيبة معززة بالعناصر الأساسية (محتوى مغذيات متوازن ومدروس لتلبية احتياجات مختلف النباتات).
- الحجم والسعة الصافية: العبوة تأتي بحجم اقتصادي سعة 1 لتر (سعة 1 لتر / 1 Litre).
📅 دليل الاستخدام والتطبيق السريع
- طرق التطبيق والاستخدام:
- التسميد والرش الورقي: يُمزج بالماء حسب نسب الخلط المعتمدة، ويُرش رذاذاً متجانساً وكاملاً على المجموع الخضري والأوراق حتى البلل الكامل (يُفضل الرش في الصباح الباكر أو عند الغروب وتجنب أوقات الظهيرة والحرارة العالية لضمان أعلى كفاءة امتصاص).
- المحاصيل المستهدفة: ممتاز جداً لجميع محاصيل البيوت المحمية (الصوب كالفلفل، الطماطم، الخيار)، الخضروات المكشوفة، أشجار الفاكهة والحمضيات، مزارع النخيل، ونباتات الزينة والزهور والمسطحات الخضراء.
⚠️ إرشادات التخزين والسلامة
- التخزين والصيانة: تُحفظ العبوة محكمة الإغلاق في مكان جاف تماماً، بارد، وجيد التهوية بعيداً عن أشعة الشمس المباشرة أو مصادر الرطوبة ومياه الأمطار للحفاظ على استقرار وثبات العناصر المغذية بالداخل.
- السلامة: منتج آمن تماماً وصديق للبيئة والنبات عند الالتزام بمعدلات الاستخدام المقررة، ويُنصح بغسل اليدين والوجه جيداً بالماء والصابون بعد الانتهاء من عمليات الخلط والتطبيق الميداني كإجراء سلامة عام.
| Category | ادوية زراعية |
|---|
No reviews yet. Be the first to review this product!
Login to write a review
Related Products
AED 100