import { INGREDIENTS, RECIPES } from '../data/catalog.js?v=2'; import { MEAL_SLOTS } from '../planner/mealSlots.js'; import { addMonths, addWeeks, sameMonth, startOfDay, startOfMonth, startOfWeekMonday, weekContains, } from '../services/dateUtils.js'; import { computeEntryNutrition, computeFullForecast, countDayShortfalls, dayHasAnyMeal, sumDayNutrition, } from '../services/planIngredients.js'; import { addOrMergeShoppingLines, loadPantry } from '../services/pantryShopping.js'; import { dateKey, getDayPlan, loadPlans, newPlanEntryId, savePlans, } from '../services/planStore.js'; import { CALENDAR_HANDLE_CLASS, CALENDAR_MONTHS_SHORT, bindCalendarDayClicks, createCalendarTopbarHTML, createCalendarWeekdayHeaderHTML, formatCalendarMonthYear, formatCalendarSelectedDate, isCalendarOnToday, renderCollapsibleCalendar, syncCalendarTodayButton, syncCollapsibleCalendarMode, } from '../ui/mealCalendar.js?v=1'; const WEEKDAYS_LONG = [ 'Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota', ]; const PLANNER_SHEET_BOTTOM_INSET = '5.25rem'; const PLANNER_SHEET_MAX_HEIGHT = '70vh'; const PLANNER_SHEET_OFF_TRANSFORM = `translateY(calc(100% + ${PLANNER_SHEET_BOTTOM_INSET}))`; function recipesForSlot(slotId) { return Object.values(RECIPES).filter((r) => r.allowedSlots.includes(slotId)); } function syncTodayButton(mode, weekStart, monthAnchor, selected) { syncCalendarTodayButton( document.getElementById('cal-go-today'), isCalendarOnToday(mode, weekStart, monthAnchor, selected), ); } export function getMealPlannerHTML() { return ` `; } function updatePeriodLabel(mode, weekStart, monthAnchor, selected) { const el = document.getElementById('cal-period-label'); if (!el) return; if (mode === 'week') { el.textContent = formatCalendarSelectedDate(selected); } else { el.textContent = formatCalendarMonthYear(monthAnchor); } } function syncModeToggle(mode) { syncCollapsibleCalendarMode({ mode, weekWrapEl: document.getElementById('calendar-week-wrap'), monthWrapEl: document.getElementById('calendar-month-wrap'), handleEl: document.getElementById('calendar-handle-icon'), }); } function bindCalendarSwipeGesture(state, rerender) { const zone = document.getElementById('calendar-swipe-zone'); if (!zone) return; let startY = 0; let ptrId = null; let moved = false; zone.addEventListener('pointerdown', (e) => { if (ptrId !== null) return; startY = e.clientY; ptrId = e.pointerId; moved = false; }); zone.addEventListener('pointermove', (e) => { if (e.pointerId !== ptrId) return; if (Math.abs(e.clientY - startY) > 10) moved = true; }); zone.addEventListener('pointerup', (e) => { if (e.pointerId !== ptrId) return; const dy = e.clientY - startY; ptrId = null; if (!moved || Math.abs(dy) < 30) return; let switched = false; if (state.mode === 'week' && dy > 30) { state.mode = 'month'; state.monthAnchor = startOfMonth(state.selected); switched = true; } else if (state.mode === 'month' && dy < -30) { state.mode = 'week'; state.weekStart = startOfWeekMonday(state.selected); switched = true; } if (switched) { zone.addEventListener('click', (ev) => { ev.stopPropagation(); ev.preventDefault(); }, { capture: true, once: true }); rerender(); } }); zone.addEventListener('pointercancel', () => { ptrId = null; moved = false; }); } function showPlannerToast(message) { const wrap = document.getElementById('planner-toast'); const text = document.getElementById('planner-toast-text'); if (!wrap || !text) return; text.textContent = message; wrap.classList.remove('opacity-0', 'translate-y-2'); wrap.classList.add('opacity-100', 'translate-y-0'); clearTimeout(showPlannerToast._t); showPlannerToast._t = setTimeout(() => { wrap.classList.add('opacity-0', 'translate-y-2'); wrap.classList.remove('opacity-100', 'translate-y-0'); }, 2600); } function openSheet(backdrop, sheet) { if (!backdrop || !sheet) return; sheet.style.visibility = 'visible'; sheet.style.transition = 'transform 300ms cubic-bezier(0.32, 0.72, 0, 1)'; sheet.style.transform = 'translateY(0)'; backdrop.classList.remove('hidden'); requestAnimationFrame(() => { backdrop.classList.remove('opacity-0'); }); } function closeSheet(backdrop, sheet) { if (!backdrop || !sheet) return; sheet.style.transition = 'transform 300ms cubic-bezier(0.32, 0.72, 0, 1)'; sheet.style.transform = PLANNER_SHEET_OFF_TRANSFORM; backdrop.classList.add('opacity-0'); setTimeout(() => { backdrop.classList.add('hidden'); sheet.style.visibility = 'hidden'; }, 300); } /** Zamykanie panelu: przeciągnięcie nagłówka w dół (pointer). */ function bindPlannerSheetDragClose(sheet, closeFn) { const zone = sheet.querySelector('[data-planner-sheet-drag-zone]'); if (!zone || !sheet) return; let startY = 0; let pulling = false; let ptrId = null; const resetVisual = () => { sheet.style.transition = 'transform 300ms cubic-bezier(0.32, 0.72, 0, 1)'; sheet.style.transform = 'translateY(0)'; }; zone.addEventListener('pointerdown', (e) => { if (e.pointerType === 'mouse' && e.button !== 0) return; pulling = true; ptrId = e.pointerId; startY = e.clientY; sheet.style.transition = 'none'; zone.setPointerCapture(e.pointerId); }); zone.addEventListener('pointermove', (e) => { if (!pulling || e.pointerId !== ptrId) return; const dy = Math.max(0, e.clientY - startY); sheet.style.transform = `translateY(${dy}px)`; }); zone.addEventListener('pointerup', (e) => { if (!pulling || e.pointerId !== ptrId) return; const dy = e.clientY - startY; pulling = false; ptrId = null; try { zone.releasePointerCapture(e.pointerId); } catch { /* ignore */ } if (dy > 56) { closeFn(); return; } resetVisual(); }); zone.addEventListener('pointercancel', () => { pulling = false; ptrId = null; resetVisual(); }); } function renderDayContent(state) { const sel = state.selected; const dayPlan = getDayPlan(state.plans, sel); const totals = sumDayNutrition(dayPlan); const setText = (id, value) => { const el = document.getElementById(id); if (el) el.textContent = value; }; const setGrams = (id, value) => { const el = document.getElementById(id); if (!el) return; el.innerHTML = value === null ? '—' : `${value}g`; }; const hasMeals = totals.mealCount > 0; setText('planner-nutrition-kcal', hasMeals ? String(totals.kcal) : '—'); setGrams('planner-nutrition-p', hasMeals ? totals.protein : null); setGrams('planner-nutrition-f', hasMeals ? totals.fat : null); setGrams('planner-nutrition-c', hasMeals ? totals.carbs : null); const ingBtn = document.getElementById('planner-open-ingredients'); if (ingBtn) { const noMeals = totals.mealCount === 0; ingBtn.disabled = noMeals; ingBtn.classList.toggle('opacity-50', noMeals); ingBtn.classList.toggle('cursor-not-allowed', noMeals); if (!noMeals) { const shortCount = countDayShortfalls(dayPlan, loadPantry()); if (shortCount > 0) { ingBtn.innerHTML = ` Składniki na ten dzień ${shortCount}`; } else { ingBtn.innerHTML = ` Składniki na ten dzień OK`; } } else { ingBtn.innerHTML = ` Składniki na ten dzień`; } } const slotsRoot = document.getElementById('planner-meal-slots'); if (!slotsRoot) return; const skipped = dayPlan._skipped || {}; slotsRoot.innerHTML = MEAL_SLOTS.map((slot) => { const isSkipped = skipped[slot.id] === true; const entries = isSkipped ? [] : (Array.isArray(dayPlan[slot.id]) ? dayPlan[slot.id] : []); let slotKcal = 0; entries.forEach((entry) => { if (entry?.recipeId && RECIPES[entry.recipeId]) slotKcal += computeEntryNutrition(entry).kcal; }); const entryCards = entries.map((entry) => { const recipe = entry && entry.recipeId ? RECIPES[entry.recipeId] : null; if (!recipe) return ''; const servings = Math.max(1, Number(entry.servings) || 1); const entryN = computeEntryNutrition(entry); const eid = escapeHtml(entry.id); const hasCustom = (entry.excludedIngredients?.length > 0) || (entry.amountOverrides && Object.keys(entry.amountOverrides).length > 0) || (entry.addedIngredients?.length > 0) || (entry.substitutions && Object.keys(entry.substitutions).length > 0); const customDot = hasCustom ? '' : ''; const servLabel = servings > 1 ? `·×${servings}` : ''; return `
${recipe.image ? `` : `${escapeHtml(recipe.thumbLabel)}`}

${escapeHtml(recipe.title)}

${customDot}

${recipe.minutes} min · ${entryN.kcal} kcal${servLabel}

`; }).join(''); const addBtn = ``; const kcalPill = slotKcal > 0 ? `${slotKcal} kcal` : ''; const filledCard = `
${slot.label} ${kcalPill} ${addBtn}
${entries.length > 0 ? `
${entryCards}
` : ''}
`; if (entries.length > 0) return filledCard; return `
${slot.label} ${addBtn}
`; }).join(''); } function escapeHtml(s) { return String(s) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } function getRecentRecipeIds(plans, limit = 5) { const seen = new Map(); const keys = Object.keys(plans).sort().reverse(); for (const key of keys) { const day = plans[key]; if (!day) continue; for (const slotId of Object.keys(day)) { if (slotId === '_skipped') continue; const entries = day[slotId]; if (!Array.isArray(entries)) continue; for (const e of entries) { if (e?.recipeId && RECIPES[e.recipeId] && !seen.has(e.recipeId)) { seen.set(e.recipeId, true); if (seen.size >= limit) return [...seen.keys()]; } } } } return [...seen.keys()]; } function recipeCardHtml(r) { return ` `; } let _pickerSlotRecipes = []; let _pickerPlans = {}; function renderPickerList(slotId, plans, query = '') { const slot = MEAL_SLOTS.find((s) => s.id === slotId); const list = document.getElementById('planner-picker-list'); const title = document.getElementById('planner-picker-title'); const sub = document.getElementById('planner-picker-sub'); if (!list || !title || !sub) return; title.textContent = 'Wybierz przepis'; sub.textContent = slot ? `Dla: ${slot.label}` : ''; const allRecipes = recipesForSlot(slotId); _pickerSlotRecipes = allRecipes; _pickerPlans = plans; const q = query.trim().toLowerCase(); const filtered = q ? allRecipes.filter((r) => r.title.toLowerCase().includes(q) || (r.tags || []).some((t) => t.toLowerCase().includes(q))) : allRecipes; if (filtered.length === 0 && q) { list.innerHTML = '

Brak wyników.

'; return; } if (filtered.length === 0) { list.innerHTML = '

Brak dopasowanych przepisów.

'; return; } let html = ''; if (!q) { const recentIds = getRecentRecipeIds(plans); const recentInSlot = recentIds.map((id) => RECIPES[id]).filter((r) => r && r.allowedSlots.includes(slotId)); if (recentInSlot.length > 0) { html += `

Ostatnio używane

`; html += recentInSlot.map(recipeCardHtml).join(''); html += `
`; html += `

Wszystkie

`; } } html += filtered.map(recipeCardHtml).join(''); list.innerHTML = html; } function plIngredientWord(n) { if (n === 1) return 'składnik'; const m10 = n % 10; const m100 = n % 100; if (m10 >= 2 && m10 <= 4 && (m100 < 12 || m100 > 14)) return 'składniki'; return 'składników'; } function updateIngButtons(state) { const btn1 = document.getElementById('planner-ing-add-all'); const btn2 = document.getElementById('planner-ing-add-btn'); const todayCount = (state._todayShortfalls || []).length; const allCount = (state._allForecastShortfalls || []).length; if (btn1) { if (todayCount > 0) { btn1.classList.remove('hidden'); btn1.disabled = false; btn1.innerHTML = ` Dodaj braki na dziś do listy`; } else { btn1.classList.add('hidden'); } } if (btn2) { if (allCount > todayCount) { btn2.classList.remove('hidden'); btn2.innerHTML = ` Dodaj braki na cały tydzień`; } else { btn2.classList.add('hidden'); } } } function renderIngredientsSheet(state) { const body = document.getElementById('planner-ing-body'); const titleEl = document.getElementById('planner-ing-title'); const subEl = document.getElementById('planner-ing-sub'); if (!body) return; const pantry = loadPantry(); const forecast = computeFullForecast(state.plans, pantry, state.selected); const today = forecast.length > 0 && forecast[0].dayIndex === 0 ? forecast[0] : null; const upcoming = forecast.filter((d) => d.dayIndex > 0 && d.hasShortfall); state._todayShortfalls = today ? today.items.filter((it) => !it.enough) : []; state._allForecastShortfalls = []; for (const d of forecast) { for (const it of d.items) { if (!it.enough) state._allForecastShortfalls.push(it); } } if (titleEl) { const wd = WEEKDAYS_LONG[state.selected.getDay()]; titleEl.textContent = `${wd}, ${state.selected.getDate()} ${CALENDAR_MONTHS_SHORT[state.selected.getMonth()]} — składniki`; } if (subEl) subEl.textContent = 'Porównanie potrzeb z zapasami w spiżarni.'; if (!today || today.items.length === 0) { body.innerHTML = '

Najpierw zaplanuj posiłki.

'; updateIngButtons(state); return; } const shortItems = today.items.filter((it) => !it.enough); const okItems = today.items.filter((it) => it.enough); let html = ''; if (shortItems.length === 0) { html += `

Wszystko masz w spiżarni

${today.items.length} ${plIngredientWord(today.items.length)} — zapasy wystarczą

`; } else { html += `

${shortItems.length} ${plIngredientWord(shortItems.length)} do kupienia

Brakuje składników na zaplanowane posiłki

`; } if (shortItems.length > 0) { html += `

Do kupienia

`; } if (okItems.length > 0) { html += `

W spiżarni

`; } if (upcoming.length > 0) { html += `

Nadchodzące braki

${upcoming.map((day) => { const wd = WEEKDAYS_LONG[day.date.getDay()]; const label = `${wd}, ${day.date.getDate()} ${CALENDAR_MONTHS_SHORT[day.date.getMonth()]}`; const shorts = day.items.filter((it) => !it.enough); return `

${escapeHtml(label)}

    ${shorts.map((it) => `
  • ${escapeHtml(it.name)} −${formatAmount(it.shortfall)} ${escapeHtml(it.pantryUnit)}
  • `).join('')}
`; }).join('')}
`; } body.innerHTML = html; updateIngButtons(state); } function formatAmount(n) { return Number.isInteger(n) ? String(n) : String(n); } function seedDemoIfEmpty(plans) { const todayKey = dateKey(new Date()); if (Object.keys(plans).length > 0) return plans; return { ...plans, [todayKey]: { sniadanie: [{ id: newPlanEntryId(), recipeId: 'jajecznica', servings: 1 }], obiad: [{ id: newPlanEntryId(), recipeId: 'makaron_ricotta', servings: 1 }], kolacja: [{ id: newPlanEntryId(), recipeId: 'kanapka_losos', servings: 1 }], }, }; } export function setupMealPlanner() { let plans = loadPlans(); plans = seedDemoIfEmpty(plans); savePlans(plans); const state = { mode: 'week', weekStart: startOfWeekMonday(new Date()), monthAnchor: startOfDay(new Date()), selected: startOfDay(new Date()), plans, pickerSlot: null, }; const weekGrid = document.getElementById('calendar-week-grid'); const monthGrid = document.getElementById('calendar-month-grid'); const pickerBackdrop = document.getElementById('planner-picker-backdrop'); const pickerSheet = document.getElementById('planner-picker-sheet'); const ingBackdrop = document.getElementById('planner-ing-backdrop'); const ingSheet = document.getElementById('planner-ing-sheet'); const rerender = () => { syncModeToggle(state.mode); updatePeriodLabel(state.mode, state.weekStart, state.monthAnchor, state.selected); syncTodayButton(state.mode, state.weekStart, state.monthAnchor, state.selected); renderCollapsibleCalendar({ weekGridEl: weekGrid, monthGridEl: monthGrid, weekAnchorDate: state.weekStart, monthAnchorDate: state.monthAnchor, selectedDate: state.selected, resolveDayState: (day, meta) => ({ dimmed: meta.mode === 'month' && !meta.inCurrentMonth, showIndicator: meta.mode === 'month' ? meta.inCurrentMonth && dayHasAnyMeal(state.plans, day) : dayHasAnyMeal(state.plans, day), }), }); renderDayContent(state); }; const persist = () => { savePlans(state.plans); rerender(); }; /* ── calendar scroll shadow ─────────────────── */ const plannerScroll = document.getElementById('planner-scroll'); const calBar = document.getElementById('planner-cal-bar'); if (plannerScroll && calBar) { const shadow = document.createElement('div'); shadow.style.cssText = 'position:absolute;left:0;right:0;bottom:-8px;height:8px;background:linear-gradient(to bottom,rgba(0,0,0,0.25),transparent);opacity:0;transition:opacity 0.2s;pointer-events:none;'; calBar.appendChild(shadow); plannerScroll.addEventListener('scroll', () => { shadow.style.opacity = plannerScroll.scrollTop > 2 ? '1' : '0'; }); } bindCalendarDayClicks(weekGrid, (date) => { state.selected = date; rerender(); }); bindCalendarDayClicks(monthGrid, (date) => { state.selected = date; rerender(); }); document.getElementById('cal-prev')?.addEventListener('click', () => { if (state.mode === 'week') { state.weekStart = addWeeks(state.weekStart, -1); if (!weekContains(state.weekStart, state.selected)) { state.selected = new Date(state.weekStart); } } else { state.monthAnchor = addMonths(state.monthAnchor, -1); if (!sameMonth(state.monthAnchor, state.selected)) { state.selected = startOfMonth(state.monthAnchor); } } rerender(); }); document.getElementById('cal-next')?.addEventListener('click', () => { if (state.mode === 'week') { state.weekStart = addWeeks(state.weekStart, 1); if (!weekContains(state.weekStart, state.selected)) { state.selected = new Date(state.weekStart); } } else { state.monthAnchor = addMonths(state.monthAnchor, 1); if (!sameMonth(state.monthAnchor, state.selected)) { state.selected = startOfMonth(state.monthAnchor); } } rerender(); }); document.getElementById('cal-go-today')?.addEventListener('click', () => { const today = startOfDay(new Date()); state.selected = today; state.weekStart = startOfWeekMonday(today); state.monthAnchor = startOfMonth(today); rerender(); }); document.getElementById('planner-meal-slots')?.addEventListener('click', (e) => { const skipBtn = e.target.closest('.planner-skip-meal'); if (skipBtn) { const slotId = skipBtn.getAttribute('data-slot-id'); if (!slotId) return; const key = dateKey(state.selected); if (!state.plans[key]) state.plans[key] = {}; if (!state.plans[key]._skipped) state.plans[key]._skipped = {}; state.plans[key]._skipped[slotId] = true; persist(); return; } const unskipBtn = e.target.closest('.planner-unskip'); if (unskipBtn) { const slotId = unskipBtn.getAttribute('data-slot-id'); if (!slotId) return; const key = dateKey(state.selected); if (state.plans[key]?._skipped) { delete state.plans[key]._skipped[slotId]; if (Object.keys(state.plans[key]._skipped).length === 0) delete state.plans[key]._skipped; if (Object.keys(state.plans[key]).length === 0) delete state.plans[key]; } persist(); return; } const openRecipe = e.target.closest('.planner-open-recipe'); if (openRecipe) { const recipeId = openRecipe.getAttribute('data-recipe-id'); if (recipeId && window.openRecipeDetail) { window.openRecipeDetail(recipeId); } return; } const addBtn = e.target.closest('.planner-add-meal'); if (addBtn) { const slotId = addBtn.getAttribute('data-slot-id'); state.pickerSlot = slotId; const searchInput = document.getElementById('planner-picker-search'); if (searchInput) searchInput.value = ''; renderPickerList(slotId, state.plans); openSheet(pickerBackdrop, pickerSheet); return; } const editBtn = e.target.closest('.planner-edit-meal'); if (editBtn) { const slotId = editBtn.getAttribute('data-slot-id'); const entryId = editBtn.getAttribute('data-entry-id'); const key = dateKey(state.selected); const arr = state.plans[key]?.[slotId]; if (!Array.isArray(arr)) return; const entry = arr.find((x) => x && x.id === entryId); if (!entry) return; window.openMealPlanEditor?.({ mode: 'edit', recipeId: entry.recipeId, date: state.selected, slotId, entry, }); return; } const clearBtn = e.target.closest('.planner-clear-meal'); if (clearBtn) { const slotId = clearBtn.getAttribute('data-slot-id'); const entryId = clearBtn.getAttribute('data-entry-id'); const key = dateKey(state.selected); const arr = state.plans[key]?.[slotId]; if (!Array.isArray(arr) || !entryId) return; const next = arr.filter((x) => x && x.id !== entryId); if (!state.plans[key]) state.plans[key] = {}; if (next.length === 0) delete state.plans[key][slotId]; else state.plans[key][slotId] = next; if (Object.keys(state.plans[key]).length === 0) delete state.plans[key]; persist(); return; } }); const closePicker = () => { state.pickerSlot = null; closeSheet(pickerBackdrop, pickerSheet); }; document.getElementById('planner-picker-search')?.addEventListener('input', (e) => { if (state.pickerSlot) { renderPickerList(state.pickerSlot, state.plans, e.target.value); } }); bindPlannerSheetDragClose(pickerSheet, closePicker); bindPlannerSheetDragClose(ingSheet, () => closeSheet(ingBackdrop, ingSheet)); pickerBackdrop?.addEventListener('click', closePicker); document.getElementById('planner-picker-list')?.addEventListener('click', (e) => { const pick = e.target.closest('.planner-pick-recipe'); if (!pick || !state.pickerSlot) return; const recipeId = pick.getAttribute('data-recipe-id'); if (!recipeId || !RECIPES[recipeId]) return; const slotId = state.pickerSlot; closePicker(); setTimeout(() => { window.openMealPlanEditor?.({ mode: 'add', recipeId, date: state.selected, slotId, }); }, 320); }); document.getElementById('planner-open-ingredients')?.addEventListener('click', () => { if (sumDayNutrition(getDayPlan(state.plans, state.selected)).mealCount === 0) return; renderIngredientsSheet(state); openSheet(ingBackdrop, ingSheet); }); ingBackdrop?.addEventListener('click', () => { closeSheet(ingBackdrop, ingSheet); }); ingSheet?.addEventListener('click', (e) => { const row = e.target.closest('.planner-ing-row'); if (!row || !ingSheet.contains(row)) return; row.classList.toggle('ingredient-active'); }); document.getElementById('planner-ing-add-all')?.addEventListener('click', () => { const items = state._todayShortfalls || []; if (items.length === 0) return; const lines = items.map((it) => ({ ingredientId: it.ingredientId, amount: it.shortfall, unit: it.pantryUnit, category: it.category, sourceNote: 'Braki z planu dnia', })); addOrMergeShoppingLines(lines); showPlannerToast(`Dodano ${lines.length} braków na listę zakupów.`); window.refreshShopping?.(); closeSheet(ingBackdrop, ingSheet); }); document.getElementById('planner-ing-add-btn')?.addEventListener('click', () => { const items = state._allForecastShortfalls || []; if (items.length === 0) return; const map = new Map(); for (const it of items) { const key = it.ingredientId; if (map.has(key)) { const cur = map.get(key); cur.amount = Math.round((cur.amount + it.shortfall) * 10) / 10; } else { map.set(key, { ingredientId: it.ingredientId, amount: it.shortfall, unit: it.pantryUnit, category: it.category, sourceNote: 'Braki z planu tygodnia', }); } } const lines = [...map.values()]; addOrMergeShoppingLines(lines); showPlannerToast(`Dodano ${lines.length} braków na listę zakupów.`); window.refreshShopping?.(); closeSheet(ingBackdrop, ingSheet); }); rerender(); window.refreshPlanner = () => { state.plans = loadPlans(); rerender(); }; bindCalendarSwipeGesture(state, rerender); requestAnimationFrame(() => { const ww = document.getElementById('calendar-week-wrap'); const mw = document.getElementById('calendar-month-wrap'); const t = 'max-height 300ms ease, opacity 200ms ease'; if (ww) ww.style.transition = t; if (mw) mw.style.transition = t; }); }