Ë
    +PkjŒ9 ã                   óÆ
  — U d dl Z d dlZd dlmZ d dlmZ d dlmZ d dl	m
Z
 d dlmZmZmZmZmZmZmZmZmZmZmZ dedefd	„Zdedz  dedz  fd
„Zd«dededz  dedz  fd„Zd«dededz  defd„Zd«dededz  defd„Zdedz  defd„Zdedee    fd„Z!d¬dede"ddfd„Z#d«dededz  de fd„Z$ ejJ                  dejL                  «      Z' ejJ                  dejL                  «      Z(dZ)dZ*de"dedede fd„Z+deddfd„Z,dede-dz  fd„Z.dedefd „Z/d«dededz  de fd!„Z0ded"ede fd#„Z1dede fd$„Z2ded%e"fd&„Z3ded%e"de fd'„Z4ded%e"d(ede fd)„Z5ded%e"de fd*„Z6ded%e"d+ede fd,„Z7dede fd-„Z8d­ded%e"d/edz  d0ede f
d1„Z9d®ded%e"d2ed0ede f
d3„Z:ded%e"de fd4„Z;d5edefd6„Z<d5edefd7„Z=d5edefd8„Z>d"ed9edefd:„Z?d¯d9ededz  d;edz  defd<„Z@dede fd=„ZAded>ede"dz  fd?„ZBd«ded9ed"edz  fd@„ZCd°ded9ede"ddfdA„ZDd«ded>edBedz  fdC„ZEdDedefdE„ZFded9ede fdF„ZG	 	 d¯ded%e"dz  d9edz  de fdG„ZHded%e"de fdH„ZId«dedIedz  defdJ„ZJdededefdK„ZKdededz  fdL„ZLdedefdM„ZMdede fdN„ZN	 	 	 d±dedPedQedz  dedz  de f
dR„ZOdededefdS„ZPdeddfdT„ZQd²dedUe"ddfdV„ZRdede fdW„ZSdXZTdedYede fdZ„ZUd[ZVdedYede fd\„ZWded]edefd^„ZXdedYedefd_„ZYdedYedefd`„ZZd²dedYedUe"ddfda„Z[dedYedefdb„Z\ddcœdeddedz  ddfde„Z]deddfdf„Z^dgZ_	 d«dhdiœdedYedjee   dz  dkede f
dl„Z`d.dmœdednedoeddfdp„Zadededz  defdq„Zb	 d³dededz  dYede fdr„Zcd]edefds„Zdd]edefdt„Zedjee   dee   fdu„ZfdvZg	 	 	 d´dedYedjee   dwedxedz  dyede fdz„ZhdedYedjee   defd{„ZidedYedjee   defd|„Zjd}Zk	 	 	 	 dµdededz  dYedjee   dz  d]ede fd~„Zl	 d¶dedYede"defd€„Zm	 d¶dedYedjee   de"def
d�„ZndedYed‚ee   dƒedef
d„„ZodedYeddfd…„Zpded]edefd†„Zqded]edefd‡„ZrdˆZsdedYedjee   de fd‰„ZtdOdOg dŠœZue evd‹<   d·dŒ„Zwdedz  d�edŽee   ddfd�„ZxdedYedjee   ddfd�„Zy	 	 	 d¸dededz  dYedjee   dz  de f
d‘„Zzd¹ded’e-defd“„Z{d”Z|	 d«dedYedjee   dz  fd•„Z}dedYedjee   dxedef
d–„Z~dedYedjee   dxedef
d—„Zdedefd˜„Z€d™Z�dšZ‚d›ZƒdœZ„deddfd�„Z…deddfdž„Z†d¹ded’e-defdŸ„Z‡	 dºded ed¡eddfd¢„Zˆ	 	 	 	 	 d»dededz  dYedŽee   dz  d]edyede fd£„Z‰d¤ZŠd¥Z‹d¼ded’e-defd¦„ZŒdeddfd§„Z�	 	 	 	 	 d½dededz  dYed¨ee    dz  d ed¡ede fd©„ZŽd«dededz  de fdª„Z�y)¾é    N)ÚPage)Úrun)Úhuman_delay)Úclose_hudl_popups)ÚLIBRARY_NAV_LINKÚLIBRARY_TILEÚMANAGE_LIBRARY_CONTENTÚMANAGE_LIBRARY_SECTIONÚPRIMARY_NAVBARÚSTORAGE_BAR_TEAM_USAGEÚSTORAGE_LEARN_MORE_TEXTÚSTORAGE_PROGRESS_BAR_CONTAINERÚSTORAGE_USAGE_CONTAINERÚSTORAGE_USAGE_EXPAND_BUTTONÚSTORAGE_USAGE_MESSAGEÚurlÚreturnc                 óB   — | j                  «       }d|v xs
 d|v xr d|v S )Nz/libraryz/watch/team/ú/analyze)Úlower)r   r   s     úC/var/www/html/sergio/sergio-backend/app/automation/tasks/library.pyÚ_is_library_pager      s,   € Ø�I‰I‹K€EØ˜ÐÒS >°UÐ#:Ò#R¸zÈUÐ?RÐSó    c                 ó^   — | sy t        j                  d| «      }|r|j                  d«      S d S )NzC/(?:watch/team|library|manage/library|manage/exchanges|teams)/(\d+)é   ©ÚreÚsearchÚgroup©r   Úmatchs     r   Ú_team_id_from_any_urlr"      s3   € ÙØÜ�I‰IØNØó€Eñ #ˆ5�;‰;�q‹>Ð,¨Ð,r   ÚpageÚteam_idc              ƒ   ó  K  — |xs dj                  «       }|j                  «       r|S t        | j                  xs d«      }|r|S 	 | j	                  d«      j
                  }|j                  «       ƒ d {  –—† dkD  rF|j                  d«      ƒ d {  –—† xs d}t        j                  d|«      }|r|j                  d«      S 	 ddlm} t        |j                  «       «      S 7 Œm7 ŒS# t        $ r Y Œ/w xY w# t        $ r Y y w xY w­w)NÚ z¦[data-qa-id^="webnav-teamswitcher-team-"].hui-primaryteamswitcher__item--selected, [data-qa-id="webnav-teamswitcher-menu"] > a.hui-primaryteamswitcher__item--selectedr   ú
data-qa-idúwebnav-teamswitcher-team-(\d+)r   )Úsession_store)ÚstripÚisdigitr"   r   ÚlocatorÚfirstÚcountÚget_attributer   r   r   Ú	ExceptionÚapp.automation.session_storer)   Úget_exchanges_url)r#   r$   ÚexplicitÚfrom_urlÚselectedÚqaÚmr)   s           r   Ú_resolve_library_team_idr8   %   sÿ   è ø€ Ø’˜2×$Ñ$Ó&€HØ×ÑÔØˆä$ T§X¡X¢^°Ó4€HÙØˆðØ—<‘<ðbó
÷ ‰%ð 	ð —‘Ó!×! AÒ%Ø×-Ñ-¨lÓ;×;ÒA¸rˆBÜ—	‘	Ð;¸RÓ@ˆAÙØ—w‘w˜q“zÐ!ðÝ>ä$ ]×%DÑ%DÓ%FÓGÐGð "øØ;ùô ò Ùðûô ò Ùðüsr   ‚ADÁ.C' Á5C#Á6C' ÂC%Â0C' ÃDÃC6 Ã"DÃ#C' Ã%C' Ã'	C3Ã0DÃ2C3Ã3DÃ6	DÃ?DÄDÄDc              ƒ   ó  K  — t        | |«      ƒ d{  –—† }|rd|› d�S d| j                  xs dvrB| j                  dd¬«      ƒ d{  –—†  t        d	d
«      ƒ d{  –—†  t	        | «      ƒ d{  –—†  | j                  t        «      j                  j                  dd¬«      ƒ d{  –—†  | j                  t        «      j                  }	 |j                  dd¬«      ƒ d{  –—†  |j                  d«      ƒ d{  –—† }|r;|j                  d«      r|S |j                  d«      rd|v r	d|vrd|› �S d|› �S d|› �S 	 t        | d«      ƒ d{  –—† }|rd|› d�S t        d«      ‚7 �ŒR7 �Œ 7 �Œ7 Œþ7 ŒÈ7 Œ�7 Œx# t        $ r Y ŒCw xY w7 Œ7­w)zJPrefer app analyze library (where tiles live), then classic /library/{id}.Nú https://app.hudl.com/watch/team/r   úhudl.com/homer&   úhttps://www.hudl.com/homeÚdomcontentloaded©Ú
wait_untilç      ø?ç      @Úvisibleé˜:  ©ÚstateÚtimeoutÚattachedé'  ÚhrefÚhttpÚ/ú	/library/ú
watch/teamúhttps://www.hudl.comúhttps://app.hudl.comúhttps://app.hudl.com/z]Could not resolve Hudl library URL dynamically. Select a team first (Playwright team switch).)r8   r   Úgotor   r   r,   r   r-   Úwait_forr   r/   Ú
startswithr0   Ú
ValueError)r#   r$   ÚtidÚnav_linkrI   s        r   Ú_resolve_library_urlrW   C   s§  è ø€ ä(¨¨wÓ7×
7€CÙ
Ø1°#°°hÐ?Ð?à˜tŸx™xš~¨2Ñ.Ø�i‰iÐ3Ð@RˆiÓS×SÐSÜ˜#˜sÓ#×#Ð#Ü Ó%×%Ð%à
�,‰,”~Ó
&×
,Ñ
,×
5Ñ
5¸IÈuÐ
5Ó
U×UÐUà�|‰|Ô,Ó-×3Ñ3€HðØ×Ñ j¸%ÐÓ@×@Ð@Ø×+Ñ+¨FÓ3×3ˆÙØ�‰˜vÔ&Ø�Ø�‰˜sÔ#Ø $Ñ&¨<¸tÑ+CØ1°$°Ð8Ð8Ø-¨d¨VÐ4Ð4Ø*¨4¨&Ð1Ð1ð ô )¨¨tÓ4×
4€CÙ
Ø1°#°°hÐ?Ð?ä
ð	8óð ð= 8ùð
 	TùØ#ùØ%øàUøð 	AøØ3ùô ò Ùðúð 5úsÏ   ‚F’E$“4FÁE'ÁFÁE*ÁFÁ.E-Á/7FÂ&E/Â'#FÃE5 Ã!E1Ã"E5 Ã:E3Ã;E5 ÄFÄE5 Ä1FÄ2E5 Ä6FÄ7E5 Ä;FÅFÅFÅ'FÅ*FÅ-FÅ/FÅ1E5 Å3E5 Å5	FÅ>FÆ FÆFc              ƒ   ó  K  — | j                  t        «      }	 t        | j                  «      r'|j	                  «       ƒ d{  –—† dkD  r| j                  S d| j                  xs dv rd| j                  xs dvr| j                  S t        | |¬«      ƒ d{  –—† }t        d|› �«       | j                  |d¬	«      ƒ d{  –—†  t        d
d«      ƒ d{  –—†  t        | «      ƒ d{  –—†  |S 7 Œª# t
        $ r Y Œtw xY w7 Œg7 Œ@7 Œ.7 Œ­w)zEOpen the Hudl library page for the active team. Returns the URL used.Nr   rL   r&   úmanage/library©r$   úNavigating to library at r=   r>   ç       @ç      @)r,   r   r   r   r.   r0   rW   ÚprintrQ   r   r   )r#   r$   Útile_locatorÚlibrary_urls       r   Ú_navigate_to_libraryra   i   só   è ø€ à—<‘<¤Ó-€LðÜ˜DŸH™HÔ%°×0BÑ0BÓ0D×*DÀqÒ*HØ—8‘8ˆOà˜4Ÿ8™8š> rÑ*Ð/?ÈÏÉÊÐTVÑ/WØ—8‘8ˆOô -¨T¸7ÔC×C€KÜ	Ð% k ]Ð
3Ô4Ø
�)‰)�KÐ,>ˆ)Ó
?×?Ð?Ü
�c˜3Ó
×ÐÜ
˜DÓ
!×!Ð!ØÐð +Eùô
 ò Ùðúð Døà?øØøØ!úsˆ   ‚D™(C. ÁC,ÁC. ÁDÁ/C. ÂDÂC=Â(DÂ>C?Â?DÃDÃDÃ%DÃ&DÃ,C. Ã.	C:Ã7DÃ9C:Ã:DÃ?DÄDÄDÚrawc                 óL  — t        | xs d«      j                  «       }|r|j                  d«      ry|j                  d«      rd|z   }t        j                  dt        j
                  «      }|j                  |«      ryt        j                  d|t        j
                  «      ry|S )z=Keep real film thumbs; drop profile/avatar/logo placeholders.r&   zdata:image/svgz//zhttps:z¡avatar|logo|icon|sprite|profile|headshot|user[-_]?photo|/users?/|gravatar|placeholder|default.?image|person|coach.?photo|team.?switcher|nav.?photo|account.?imagez[_-](32|40|48|64|72|96)[x._-])Ústrr*   rS   r   ÚcompileÚIr   )rb   r   Úbads      r   Ú_normalize_thumbnail_urlrh   }   sˆ   € ä
ˆcŠi�R‹.×
Ñ
Ó
 €CÙ�#—.‘.Ð!1Ô2ØØ
‡~�~�dÔØ˜‰nˆÜ
�*‰*ð	4ô 	�‰ó	€Cð ‡z�z�#„Øä	‡y�yÐ1°3¼¿¹Ô=ØØ€Jr   c              ƒ   óì   K  — | j                  d«      ƒ d{  –—† }|s(	 | j                  d«      ƒ d{  –—† }t        d|› �«       |xs g S 7 Œ47 Œ# t        $ r}t        d|› �«       Y d}~Œ&d}~ww xY w­w)zAScrape modern analyze library cards + classic /library/{id} rows.a¦(  () => {
          const abs = (href) => {
            if (!href) return '';
            if (href.startsWith('http')) return href;
            if (href.startsWith('//')) return 'https:' + href;
            if (href.startsWith('/')) {
              if (location.hostname.includes('app.hudl')) return 'https://app.hudl.com' + href;
              return location.origin + href;
            }
            return href;
          };

          const BAD_THUMB = /avatar|logo|icon|sprite|profile|headshot|user[-_]?photo|\/users?\/|gravatar|placeholder|default.?image|person|coach|team.?switcher|nav.?photo|account.?image/i;
          const TYPE_RE = /\b(GAME|SCOUT|PRACTICE|SCRIMMAGE|HIGHLIGHT|OPPONENT|OTHER)\b/i;
          const DATE_RE = /\b(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[a-z]*\s+\d{1,2},?\s+\d{4}\b/i;

          const isBadThumb = (src) => {
            if (!src || typeof src !== 'string') return true;
            if (src.startsWith('data:image/svg')) return true;
            if (BAD_THUMB.test(src)) return true;
            if (/[_-](32|40|48|64|72|96)[x._-]/.test(src)) return true;
            return false;
          };

          const fromSrcset = (el) => {
            const ss = el.getAttribute('srcset') || el.getAttribute('data-srcset') || '';
            if (!ss) return '';
            let best = '', bestW = 0;
            ss.split(',').forEach((part) => {
              const bits = part.trim().split(/\s+/);
              const u = bits[0] || '';
              const w = parseInt((bits[1] || '').replace('w', ''), 10) || 0;
              if (u && !isBadThumb(u) && w >= bestW) { best = u; bestW = w; }
            });
            return best;
          };

          /** Prefer real film stills (bg-image / large img), never profile photos. */
          const pickThumb = (root) => {
            if (!root) return '';
            const scored = [];

            root.querySelectorAll('img').forEach((img) => {
              const src = abs(
                fromSrcset(img) ||
                img.currentSrc ||
                img.getAttribute('src') ||
                img.getAttribute('data-src') ||
                img.getAttribute('data-lazy-src') ||
                ''
              );
              if (isBadThumb(src)) return;
              const r = img.getBoundingClientRect();
              const area = Math.max(0, r.width) * Math.max(0, r.height);
              // Skip tiny circular avatars even if URL looks ok
              if (r.width > 0 && r.height > 0 && (r.width < 72 || r.height < 48)) return;
              if (Math.abs(r.width - r.height) < 8 && r.width < 120) return; // circle avatar
              scored.push({ src, area, w: r.width, h: r.height });
            });

            const nodes = [root, ...root.querySelectorAll('*')];
            for (const n of nodes) {
              let bg = '';
              try { bg = getComputedStyle(n).backgroundImage || ''; } catch (_) {}
              const m = bg && bg.match(/url\(["']?(.*?)["']?\)/i);
              if (!m || !m[1] || m[1] === 'none') continue;
              const src = abs(m[1]);
              if (isBadThumb(src)) continue;
              const r = n.getBoundingClientRect();
              if (r.width < 90 || r.height < 55) continue;
              scored.push({ src, area: r.width * r.height, w: r.width, h: r.height });
            }

            // Also check inline style background-image (before paint / computed empty)
            root.querySelectorAll('[style*="background"]').forEach((n) => {
              const st = n.getAttribute('style') || '';
              const m = st.match(/background(?:-image)?\s*:\s*url\(["']?(.*?)["']?\)/i);
              if (!m || !m[1]) return;
              const src = abs(m[1]);
              if (isBadThumb(src)) return;
              const r = n.getBoundingClientRect();
              scored.push({ src, area: Math.max(r.width * r.height, 1), w: r.width, h: r.height });
            });

            scored.sort((a, b) => b.area - a.area);
            // Prefer landscape film thumbs over square
            const landscape = scored.find((s) => s.w >= s.h * 1.15);
            return (landscape || scored[0] || {}).src || '';
          };

          const out = [];
          const seen = new Set();
          const push = (item) => {
            let url = abs(item.watch_url || '');
            const title = (item.title || 'Video').trim().replace(/\s+/g, ' ').slice(0, 200);
            if (!title || title.length < 2) return;
            const thumb = abs(item.thumbnail_url || '');
            const key = `${title.toLowerCase()}|${item.date || ''}`;
            if (seen.has(key)) return;
            seen.add(key);
            if (!url) {
              const m = location.pathname.match(/\/watch\/team\/(\d+)/);
              url = m
                ? `https://app.hudl.com/watch/team/${m[1]}/analyze`
                : location.href;
            }
            out.push({
              title,
              watch_url: url,
              thumbnail_url: isBadThumb(thumb) ? '' : thumb,
              type: (item.type || 'GAME').toUpperCase(),
              duration: item.duration || '00:00',
              date: item.date || '',
            });
          };

          const parseCardText = (text) => {
            const lines = text.split('\n').map((s) => s.trim()).filter(Boolean);
            const typeLine = lines.find((l) => TYPE_RE.test(l)) || 'GAME';
            const dateLine = lines.find((l) => DATE_RE.test(l)) || '';
            const titleLine =
              lines.find(
                (l) =>
                  l !== typeLine &&
                  l !== dateLine &&
                  !/^\d+\s*Items?$/i.test(l) &&
                  !/newest uploads|oldest uploads|search|filter/i.test(l) &&
                  l.length > 2
              ) || 'Video';
            return { typeLine, dateLine, titleLine };
          };

          // --- 1) Legacy tile attribute ---
          document.querySelectorAll('[data-qa-id="tile-library-item"]').forEach((tile) => {
            const a = tile.querySelector('a[href]');
            push({
              title: tile.querySelector('a p span, a p, [class*="title"]')?.innerText || a?.innerText || 'Video',
              watch_url: a?.getAttribute('href') || '',
              thumbnail_url: pickThumb(tile),
              type: tile.querySelector('[class*="label"], .label')?.innerText || 'GAME',
              duration: (tile.querySelector('[class*="duration"]')?.innerText || '00:00').trim(),
              date: (tile.querySelector('div > p, [class*="date"]')?.innerText || '').trim(),
            });
          });
          if (out.length) return out;

          // --- 2) Any data-qa-id that looks like a library/media/cutup card ---
          const qaCards = document.querySelectorAll(
            '[data-qa-id*="tile" i], [data-qa-id*="library-item" i], [data-qa-id*="cutup" i], ' +
            '[data-qa-id*="media-card" i], [data-qa-id*="video-card" i], [data-qa-id*="grid-item" i]'
          );
          qaCards.forEach((tile) => {
            const text = (tile.innerText || '').trim();
            if (!text || text.length < 3) return;
            if (!TYPE_RE.test(text) && !pickThumb(tile)) return;
            const { typeLine, dateLine, titleLine } = parseCardText(text);
            const a = tile.closest('a[href]') || tile.querySelector('a[href]');
            push({
              title: titleLine,
              watch_url: a?.getAttribute('href') || '',
              thumbnail_url: pickThumb(tile),
              type: (typeLine.match(TYPE_RE) || ['GAME'])[0],
              date: dateLine,
            });
          });
          if (out.length) return out;

          // --- 3) Visual cards: film thumb (img or CSS bg) + Game/Scout label ---
          const main =
            document.querySelector('[class*="Library"], [class*="library"], main, [role="main"]') ||
            document.body;

          // Walk candidate card roots that contain a type label
          const candidates = new Set();
          main.querySelectorAll('article, li, [class*="Card"], [class*="card"], [class*="Tile"], [class*="tile"], [role="listitem"]').forEach((el) => {
            const t = (el.innerText || '').trim();
            if (TYPE_RE.test(t) && t.length > 8 && t.length < 600) candidates.add(el);
          });
          // Also from large landscape media nodes
          main.querySelectorAll('img, [style*="background"]').forEach((node) => {
            const r = node.getBoundingClientRect();
            if (r.width < 120 || r.height < 70) return;
            let card = node.parentElement;
            for (let i = 0; i < 8 && card; i++) {
              const t = (card.innerText || '').trim();
              if (TYPE_RE.test(t) && t.length < 600) {
                candidates.add(card);
                break;
              }
              card = card.parentElement;
            }
          });

          candidates.forEach((card) => {
            const text = (card.innerText || '').trim();
            if (!TYPE_RE.test(text)) return;
            const lines = text.split('\n').map((s) => s.trim()).filter(Boolean);
            if (lines.length < 2) return;
            const { typeLine, dateLine, titleLine } = parseCardText(text);
            const a = card.closest('a[href]') || card.querySelector('a[href]');
            const thumb = pickThumb(card);
            // Skip rows that only have a profile photo (no film thumb)
            push({
              title: titleLine,
              watch_url: a?.getAttribute('href') || '',
              thumbnail_url: thumb,
              type: (typeLine.match(TYPE_RE) || ['GAME'])[0],
              date: dateLine,
            });
          });
          if (out.length) return out;

          // --- 4) Classic library links ---
          document.querySelectorAll(
            'a[href*="/videos/"], a[href*="/video/"], a[href*="/watch/"]'
          ).forEach((a) => {
            const href = a.getAttribute('href') || '';
            if (/webnav|teamswitcher|login|support|manage\/library/i.test(href)) return;
            const title = (a.innerText || a.getAttribute('title') || '').trim().split('\n')[0];
            if (!title || /^(library|home|upload)$/i.test(title)) return;
            push({ title, watch_url: href, thumbnail_url: pickThumb(a) });
          });

          return out;
        }NaÖ  () => ({
                  url: location.href,
                  hasItems: /\d+\s*Items?/i.test(document.body?.innerText || ''),
                  imgCount: document.querySelectorAll('img').length,
                  qaSample: [...new Set([...document.querySelectorAll('[data-qa-id]')]
                    .map(e => e.getAttribute('data-qa-id')).filter(Boolean))].slice(0, 40),
                  snippet: (document.body?.innerText || '').slice(0, 300),
                })u   Library scrape empty â€” meta=u&   Library scrape empty â€” meta failed: )Úevaluater^   r0   )r#   ÚrowsÚmetaÚexcs       r   Ú_scrape_library_videos_jsrn   ’   s—   è ø€ à—‘ð`	ób÷ b€DñF ð	BØŸ™ðó	÷ 	ˆDô Ð2°4°&Ð9Ô:ð Š:�2ÐðgbøðL	ùô ò 	BÜÐ:¸3¸%Ð@×AÑAûð	BüsH   ‚A4—A˜A4ŸA ³A´A ÁA4ÁA Á	A1ÁA,Á'A4Á,A1Á1A4Ú
timeout_msc              ƒ   ó¢  K  — 	 | j                  d|¬«      ƒ d{  –—†  t        dd«      ƒ d{  –—†  	 | j                  j	                  dd«      ƒ d{  –—†  t        dd	«      ƒ d{  –—†  | j                  j	                  dd
«      ƒ d{  –—†  t        dd«      ƒ d{  –—†  y7 ŒŠ# t        $ r Y Œ“w xY w7 Œ‡7 Œd7 ŒR7 Œ07 Œ# t        $ r Y yw xY w­w)z7Wait for analyze SPA tiles / 'N Items' header to paint.aW  () => {
              const t = document.body?.innerText || '';
              if (/\d+\s*Items?/i.test(t)) return true;
              if (document.querySelectorAll('[data-qa-id="tile-library-item"]').length > 0) return true;
              if (document.querySelectorAll('img').length > 12) return true;
              return false;
            }©rF   Nçš™™™™™é?çffffffö?r   i°  ç      à?çÍÌÌÌÌÌì?i¨ýÿÿç333333Ó?ç333333ã?)Úwait_for_functionr0   r   ÚmouseÚwheel)r#   ro   s     r   Ú_wait_for_library_contentr{   Š  sÜ   è ø€ ðØ×$Ñ$ðð ð %ó 	
÷ 		
ð 		
ô �c˜3Ó
×ÐðØ�j‰j×Ñ˜q $Ó'×'Ð'Ü˜#˜sÓ#×#Ð#Ø�j‰j×Ñ˜q $Ó'×'Ð'Ü˜#˜sÓ#×#Ñ#ð%		
ùô ò Ùðúàøð 	(øØ#øØ'øØ#ùÜò Ùðüs¬   ‚C„B' šB%›B' ŸC®B6¯C´C  ÁB8ÁC  Á'B:Á(#C  ÂB<ÂC  ÂB>Â C  Â$CÂ%B' Â'	B3Â0CÂ2B3Â3CÂ8C  Â:C  Â<C  Â>C  Ã 	CÃ	CÃCÃCc              ƒ   óè  ‡‡K  — ddl m}  || «      ƒ d{  –—†  t        | «      ƒ d{  –—†  t        | |«      ƒ d{  –—† }g }|r|j	                  d|› d�d|› �g«       t        | j                  xs d«      r|j                  d| j                  «       t        «       }g }|D ],  }|sŒ||vsŒ|j                  |«       |j                  |«       Œ. |s%	 |j                  t        | |¬«      ƒ d{  –—† «       dt        t           ddfˆfd„Šg }	d}
|D �]i  }g Šd8ˆˆfd„}	 | j!                  d|«       t#        d|› �«       | j%                  |d¬«      ƒ d{  –—†  t'        dd«      ƒ d{  –—†  t        | «      ƒ d{  –—†  t)        | d¬«      ƒ d{  –—†  t'        dd«      ƒ d{  –—†  | j                  }
t+        | «      ƒ d{  –—† }|r|}	nv‰rtt        «       }‰D ]N  }|j-                  d«      xs dj/                  «       }|r||v rŒ-|j                  |«       |	j                  |«       ŒP t#        dt1        |	«      › �«       |	r	 	 | j3                  d|«        �n!d|v �r| j5                  d¬«      ƒ d{  –—†  t'        dd«      ƒ d{  –—†  t        | «      ƒ d{  –—†  t)        | «      ƒ d{  –—†  t+        | «      ƒ d{  –—† }|r$|}	| j                  }
	 	 | j3                  d|«        n�‰rw|	sut        «       }‰D ]N  }|j-                  d«      xs dj/                  «       }|r||v rŒ-|j                  |«       |	j                  |«       ŒP |	r	 	 | j3                  d|«        n	 | j3                  d|«       �Œl g }t7        |	«      D ]÷  \  }}|j-                  d«      xs d}|r;t        |«      j9                  d«      s!t        |«      j9                  d «      rd!|› �n|}|s|rd|› d�}t;        |j-                  d"«      «      }|j                  |d#z   |j-                  d«      xs d$|d#z   › �|||j-                  d%«      xs d&j=                  «       |j-                  d'«      xs d(|j-                  d)«      xs dd*œ«       Œù |D �cg c]  }|j-                  d"«      rŒ|‘Œ }}|r²| �°	 | j?                  d+«      ƒ d{  –—† }tA        |t        «      r†|r„|D ]`  }|j-                  d"«      rŒ|j-                  d«      xs d}||v r	||   |d"<   Œ7|jC                  «       D ]  \  }}|sŒ	||v s||v sŒ||d"<    Œ` Œb t#        d,tE        d-„ |D «       «      › d.�«       tE        d0„ |D «       «      }t#        d1t1        |«      › d2|› �«       d3|t1        |«      |
xs | r| j                  nd||rd4t1        |«      › d5�d7œS d6d7œS 7 �Œª7 �Œš7 �Œ‰7 �ŒÙ# t        $ r}d	g dt        |«      d
œcY d}~S d}~ww xY w7 �Œš7 �Œ‰7 �Œy7 �Œg7 �ŒV7 �Œ:# t        $ r Y �Œ³w xY w7 �Œš7 �Œ‰7 �Œy7 �Œi7 �ŒY# t        $ r Y �Œ?w xY w# t        $ r Y �ŒÖw xY w# t        $ r?}t#        d|› d|› �«       Y d}~	 | j3                  d|«       �Œb# t        $ r Y �Œnw xY wd}~ww xY w# t        $ r Y �Œ†w xY w# 	 | j3                  d|«       w # t        $ r Y w w xY wxY wc c}w 7 �Œ# t        $ r}t#        d/|› �«       Y d}~�Œ�d}~ww xY w­w)9z¨
    Navigate to Hudl Library (analyze tiles first) and scrape video cards.
    Also captures JSON API payloads while the SPA loads (more reliable than DOM alone).
    r   ©Úensure_hudl_sessionNr:   r   úhttps://www.hudl.com/library/r&   rZ   Úerror)ÚstatusÚvideosr.   ÚmessageÚaccr   c                 óB	  •— | €yt        | t        «      r| D ]  } ‰||«       Œ yt        | t        «      sy| j                  d«      xs7 | j                  d«      xs$ | j                  d«      xs | j                  d«      }| j                  d«      xsJ | j                  d«      xs7 | j                  d«      xs$ | j                  d	«      xs | j                  d
«      }| j                  d«      xs; | j                  d«      xs( | j                  d«      xs | j                  d«      xs d}| j                  d«      xst | j                  d«      xsa | j                  d«      xsN | j                  d«      xs; | j                  d«      xs( | j                  d«      xs | j                  d«      xs d}t        |t        «      ra|j                  d«      xsN |j                  d«      xs; |j                  d«      xs( |j                  d«      xs |j                  d«      xs d}t        |t        «      r*|r(|d   }t        |t        «      r|j                  d«      n|}t	        t        |xs d«      «      }| j                  d«      xs; | j                  d«      xs( | j                  d«      xs | j                  d«      xs d}t        |t        t        f«      rt        |«      }t        |«      xrl t        |«      xs_ t        |«      xsR t        | j                  d «      «      xs6 t        | j                  d!«      «      xs t        | j                  d"«      «      }	t        |«      }
|	rù|
rGt        j                  d#|
t        j                  «      s"| j                  d!«      s| j                  d"«      r°|j                  t        |«      j                  «       dd$ t        |xs d«      j                  «       t        |xs d«      j                  «       |
rt        |
«      d%k  r|
j                  «       nd&t        | j                  d'«      xs d(«      t        |«      dd) d*œ«       | j!                  «       D ])  \  }}|d+v st        |t        t        f«      sŒ! ‰||«       Œ+ y),z7Recursively pull video-like objects from Hudl API JSON.NÚtitleÚnameÚdisplayNameÚ
videoTitleÚwatchUrlÚ	watch_urlr   Ú	permalinkrI   ÚcontentTypeÚtypeÚ	videoTypeÚlabelr&   ÚthumbnailUrlÚthumbnail_urlÚ	thumbnailÚ	posterUrlÚposterÚimageUrlÚ
previewUrlÚlargeÚmediumÚsmallÚsrcr   ÚdateÚ
uploadedAtÚ	createdAtÚcaptureDateÚidÚcutupIdÚvideoIdz8game|scout|practice|scrimmage|highlight|video|cutup|filméÈ   é   ÚGAMEÚdurationú00:00é(   )r†   r‹   r’   rŽ   r¦   rœ   )	ÚitemsÚresultsr‚   ÚcutupsÚnodesÚedgesÚdataÚlibraryItemsÚmedia)Ú
isinstanceÚlistÚdictÚgetrh   rd   ÚintÚfloatÚboolr   r   rf   Úappendr*   ÚlenÚupperr©   )r®   r„   Úitemr†   ÚwatchÚcontent_typeÚthumbr-   Údate_valÚlooks_videoÚtype_sÚkeyÚvalÚ_videos_from_json_blobs                €r   rÄ   z2get_library_videos.<locals>._videos_from_json_blobÌ  sR  ø€ àˆ<ØÜ�dœDÔ!Û�Ù& t¨SÕ1ð àÜ˜$¤Ô%Øð �H‰H�WÓò &Ø�x‰x˜Óò&à�x‰x˜Ó&ò&ð �x‰x˜Ó%ð	 	ð �H‰H�ZÓ ò  Ø�x‰x˜Ó$ò à�x‰x˜‹ò ð �x‰x˜Ó$ò ð �x‰x˜Óð 	ð �H‰H�]Ó#ò Ø�x‰x˜Óòà�x‰x˜Ó$òð �x‰x˜Ó òð ð 	ð �H‰H�^Ó$ò Ø�x‰x˜Ó(òà�x‰x˜Ó$òð �x‰x˜Ó$òð �x‰x˜Ó!ò	ð
 �x‰x˜
Ó#òð �x‰x˜Ó%òð ð 	ô �eœTÔ"à—	‘	˜%Ó ò Ø—9‘9˜WÓ%òà—9‘9˜XÓ&òð —9‘9˜WÓ%òð —9‘9˜UÓ#ò	ð
 ð ô �eœTÔ"¡uØ˜!‘HˆEÜ(2°5¼$Ô(?�E—I‘I˜eÔ$ÀUˆEÜ(¬¨Uª[°bÓ)9Ó:ˆà�H‰H�VÓò Ø�x‰x˜Ó%òà�x‰x˜Ó$òð �x‰x˜Ó&òð ð 	ô �h¤¤e Ô-Ü˜8“}ˆHä˜5“kò 
Ü�‹Kò )Ü�E‹{ò)ä�D—H‘H˜T“NÓ#ò)ô �D—H‘H˜YÓ'Ó(ò)ô �D—H‘H˜YÓ'Ó(ð 	ô �\Ó"ˆÙÙÜ�y‰yØKØÜ—‘ôð
 �x‰x˜	Ô"Ø�x‰x˜	Ô"à�J‰Jä  ›Z×-Ñ-Ó/°°Ð5Ü!$ U¢[¨bÓ!1×!7Ñ!7Ó!9Ü%(¨ª°"Ó%5×%;Ñ%;Ó%=ñ "¤c¨&£k°BÒ&6ð Ÿ™œà#ä # D§H¡H¨ZÓ$8Ò$C¸GÓ DÜ ›M¨#¨2Ð.ñôð Ÿ
™
ž‰HˆC�Øð 
ñ 
ô ˜C¤$¬ Õ.Ù& s¨CÕ0ñ %r   c              “   óh  •‡K  — 	 | j                   dk7  ry | j                  xs dj                  «       Š| j                  j	                  d«      xs dj                  «       }d|vrd‰vry t        ˆfd„dD «       «      sy | j                  «       ƒ d {  –—† } ‰|‰«       y 7 Œ# t        $ r Y y w xY w­w)Nr£   r&   zcontent-typeÚjsonÚgraphqlc              3   ó&   •K  — | ]  }|‰v –— Œ
 y ­w©N© )Ú.0ÚkÚrurls     €r   Ú	<genexpr>z;get_library_videos.<locals>._on_response.<locals>.<genexpr>H  s!   øè ø€ ð ñ
˜ð ˜”Iñ
ùs   ƒ)	ÚcutupÚlibraryr°   ÚvideoÚfootagerÇ   Úanalyzer¼   Úcontent)r�   r   r   Úheadersr´   ÚanyrÆ   r0   )ÚresponseÚctyper®   rÍ   rÄ   Ú
api_videoss      @€€r   Ú_on_responsez(get_library_videos.<locals>._on_response@  s²   ùè ø€ ðØ—?‘? cÒ)ØØ Ÿ™Ò*¨×1Ñ1Ó3�Ø!×)Ñ)×-Ñ-¨nÓ=ÒCÀ×JÑJÓL�Ø Ñ&¨9¸DÑ+@ØÜó ñ
óô ð Ø%Ÿ]™]›_×,�Ù& t¨ZÕ8ð -ùäò Ùðüs]   „B2†B# •B2–AB# Á)B2Á*B# Á>B2Á?B# ÂB!ÂB# Â B2Â!B# Â#	B/Â,B2Â.B/Â/B2r×   r[   r=   r>   r@   rA   é¨a  ©ro   r†   z#Library videos from API intercept: rÓ   r\   ç      @zLibrary scrape failed for ú: r‹   rJ   rK   rO   r’   r   zVideo rŽ   r¥   r¦   r§   rœ   )r    r†   r‹   r’   rŽ   r¦   rœ   a	  () => {
                  const TYPE_RE = /\b(GAME|SCOUT|PRACTICE|SCRIMMAGE|HIGHLIGHT|OPPONENT|OTHER)\b/i;
                  const BAD = /avatar|logo|icon|sprite|profile|headshot|user|gravatar|placeholder/i;
                  const out = {};
                  const cards = [];
                  document.querySelectorAll('article, li, [class*="Card"], [class*="card"], [class*="Tile"], [class*="tile"]').forEach((el) => {
                    const t = (el.innerText || '').trim();
                    if (!TYPE_RE.test(t) || t.length < 8 || t.length > 600) return;
                    const lines = t.split('\n').map(s => s.trim()).filter(Boolean);
                    const typeLine = lines.find(l => TYPE_RE.test(l)) || '';
                    const title = lines.find(l => l !== typeLine && l.length > 2 && !/^\d+\s*Items?/i.test(l)) || '';
                    if (!title) return;
                    cards.push({ el, title });
                  });
                  const canvasFromImg = (img) => {
                    try {
                      const w = Math.min(img.naturalWidth || img.width, 480);
                      const h = Math.min(img.naturalHeight || img.height, 270);
                      if (w < 80 || h < 45) return '';
                      const c = document.createElement('canvas');
                      c.width = w; c.height = h;
                      c.getContext('2d').drawImage(img, 0, 0, w, h);
                      return c.toDataURL('image/jpeg', 0.72);
                    } catch (_) { return ''; }
                  };
                  for (const { el, title } of cards) {
                    if (out[title]) continue;
                    let dataUrl = '';
                    el.querySelectorAll('img').forEach((img) => {
                      if (dataUrl) return;
                      const src = img.currentSrc || img.src || '';
                      if (BAD.test(src)) return;
                      const r = img.getBoundingClientRect();
                      if (r.width < 90 || r.height < 55) return;
                      if (Math.abs(r.width - r.height) < 8 && r.width < 120) return;
                      dataUrl = canvasFromImg(img);
                    });
                    if (dataUrl) out[title] = dataUrl;
                  }
                  return out;
                }zLibrary thumbs: filled c              3   ó|   K  — | ]4  }t        |j                  d «      xs d«      j                  d«      sŒ1d–— Œ6 y­w)r’   r&   zdata:r   N)rd   r´   rS   ©rË   Úvs     r   rÎ   z%get_library_videos.<locals>.<genexpr>ç  sB   è ø€ ð  2E¹]¸ÌcÐRS×RWÑRWÐXgÓRhÒRnÐlnÓNo×NzÑNzð  |Cõ  OD´!¹]ùs   ‚2<µ<z
 data-URLsz'Library data-URL thumb capture failed: c              3   óD   K  — | ]  }|j                  d «      sŒd–— Œ y­w)r’   r   N©r´   rà   s     r   rÎ   z%get_library_videos.<locals>.<genexpr>ì  s   è ø€ ÐG™}˜!°·±°oÕ0F”A™}ùs   ‚ ™ zLibrary videos=z with_thumbs=ÚokúFound z library videos.zNo library items found.)r�   r‚   r.   r   r$   rƒ   ©r   N)#Úapp.automation.tasks.loginr~   r   r8   Úextendr   r   ÚinsertÚsetÚaddr¸   rW   r0   rd   r²   r³   Úonr^   rQ   r   r{   rn   r´   r   r¹   Úremove_listenerÚreloadÚ	enumeraterS   rh   rº   rj   r±   r©   Úsum)r#   r$   r~   rU   Úcandidate_urlsÚ	seen_urlsÚurlsÚurm   r‚   Úused_urlr   rÚ   ÚscrapedÚseen_trá   rÂ   Úresult_videosÚir¼   r¾   ÚmissingÚdata_thumbsr†   rÌ   rÃ   Ú	thumbs_okrÄ   rÙ   s                              @@r   Úget_library_videosrý   ¤  s1  ùè ø€ õ
 ?á
˜dÓ
#×#Ð#Ü
˜DÓ
!×!Ð!ä(¨¨wÓ7×
7€CØ "€NÙ
Ø×Ñà2°3°%°xÐ@Ø/°¨uÐ5ðô	
ô ˜Ÿ™š BÔ'Ø×Ñ˜a §¡Ô*ä›%€IØ€DÛˆÚ�˜)Ò#Ø�M‰M˜!ÔØ�K‰K˜�Nð ñ
 ð	Ø�K‰KÔ2°4ÀÔE×EÔFðm1¬$¬t©*ð m1¸õ m1ð^ €FØ€HÜˆØ!#ˆ
ö	ð87	Ø�G‰G�J Ô-ÜÐ-¨c¨UÐ3Ô4Ø—)‘)˜CÐ,>�)Ó?×?Ð?Ü˜c 3Ó'×'Ð'Ü# DÓ)×)Ð)Ü+¨D¸UÔC×CÐCä˜c 3Ó'×'Ð'Ø—x‘xˆHä5°dÓ;×;ˆGÙØ ‘Ùä#&£5�Û#�AØŸ5™5 ›>Ò/¨R×6Ñ6Ó8�CÙ #¨¡-Ø Ø—J‘J˜s”OØ—M‘M !Õ$ð $ô Ð;¼CÀ»K¸=ÐIÔJáØð4Ø×$Ñ$ Z°Ô>úð3 ˜CÒØ—k‘kÐ-?�kÓ@×@Ð@Ü! # sÓ+×+Ð+Ü'¨Ó-×-Ð-Ü/°Ó5×5Ð5Ü 9¸$Ó ?×?�ÙØ$�FØ#Ÿx™x�HØðØ×$Ñ$ Z°Ô>ùñ ¡fÜ ›U�FÛ'˜Ø Ÿu™u W›~Ò3°×:Ñ:Ó<˜Ù" c¨V¡mØ$ØŸ
™
 3œØŸ™ aÕ(ð (ñ Øð
Ø×$Ñ$ Z°Ô>ùðØ×$Ñ$ Z°Ö>ði ðp €MÜ˜&Ö!‰ˆˆ1Ø—‘�kÓ"Ò( bˆÙœ˜U›×.Ñ.¨vÔ6Ü69¸%³j×6KÑ6KÈCÔ6PÐ*¨5¨'Ñ2ÐV[ˆEÙ™Ø6°s°e¸8ÐDˆEÜ(¨¯©¨Ó)?Ó@ˆØ×Ñà˜!‘eØŸ™˜w›Ò;¨V°A¸±E°7Ð+;Ø"Ø!&ØŸ™˜v›Ò0¨&×7Ñ7Ó9ØŸE™E *Ó-Ò8°ØŸ™˜f›Ò+¨ñõ
	
ð "ñ* (ÓF™-�Q¨q¯u©u°_Õ/EŠq˜-€GÐFÙ�4Ð#ð=	CØ $§¡ð(ó*!÷ *ˆKôV ˜+¤tÔ,±Û&�AØ—u‘u˜_Ô-Ø ØŸE™E '›NÒ0¨b�EØ Ñ+Ø-8¸Ñ-?˜˜/Ò*ð '2×&7Ñ&7Ö&9™F˜A˜sÚ$¨%°1©*¸¸Uº
Ø58  /Ñ 2Ù %ñ ':ð 'ô Ø-¬cñ  2E¹]ó  2Eó  /Eð  .Fð  FPð  Qôô ÑG™}ÓGÓG€IÜ	ˆOœC Ó.Ð/¨}¸Y¸KÐ
HÔIð ØÜ�]Ó#ØÒ5©˜DŸHšH°"Øñ ð ”S˜Ó'Ð(Ð(8Ð9ñð ð +ñð ðI
 $ùØ!ùà
7ùð* FúÜò 	à!ØØÜ˜s›8ñ	õ ûð	úðv @ùØ'ùØ)ùØCùà'ùð <úôV ò Úðúð3 AùØ+ùØ-ùØ5ùØ?úô* ò Úðû”9ò Úðûô ò 	ÜÐ.¨s¨e°2°c°UÐ;Ô<ÛðØ×$Ñ$ Z°Ö>øÜò Úðþð	ûô ò ÚðûðØ×$Ñ$ Z°Õ>øÜò Ùðÿò2 Gð*úôv ò 	CÜÐ;¸C¸5ÐA×BÒBûð	Cüs·  „[2–W —[2©Wª[2½W¾A*[2Â)[2Â.&[2ÃW Ã0W	Ã1W Ã9([2Ä"6YÅW2ÅYÅ,W5Å-YÅ?W8Æ YÆW;ÆYÆ(W>Æ)YÇXÇBYÉXÉ[2É YÉ:XÉ;YÊXÊYÊ!XÊ"YÊ4XÊ5YËX ËYËX#Ë0[2Ë2A#YÍX3Í)[2Í,ZÍ>D[2Ò[Ò#[Ò'[2Ò.[ Ó[
ÓA'[ Ô+[ Ô4)[ ÕA$[2×[2×[2×	W ×	W/×W*×$W/×%[2×*W/×/[2×2Y×5Y×8Y×;Y×>YØYØ	XØ[2ØXØ[2ØYØYØYØYØ YØ#	X0Ø,[2Ø/X0Ø0[2Ø3	Y Ø<[2Ø?Y Ù [2Ù	ZÙZÙZÙ"Y6Ù4[2Ù6	ZÙ?[2ÚZÚ[2ÚZÚZÚ	ZÚ[2ÚZÚ[2Ú[Ú Z3Ú2[Ú3	Z?	Ú<[Ú>Z?	Ú?[Û[2Û
[ Û	[/Û[*Û$[2Û*[/Û/[2z/(\d+)\s+of\s+([\d.]+)\s+hrs\s+\((\d+)%\)\s+usedzwidth:\s*([\d.]+)%éZ   z?Your Hudl library is full. Please free up some space to upload.ÚpercentÚ
used_hoursÚtotal_hoursc                 ó>   — | t         k\  }d||| |dœ}|r	t        |d<   |S )NT)Ú	availabler   r  Úusage_percentÚ
show_popuprƒ   )Ú!STORAGE_WARNING_THRESHOLD_PERCENTÚSTORAGE_FULL_MESSAGE)rÿ   r   r  r  Úresults        r   Ú_build_storage_usage_resultr	    s9   € ð
 Ô=Ñ=€JàØ Ø"Ø Ø ñ€Fñ Ü0ˆˆyÑØ€Mr   c              ƒ   ó  K  — | j                  t        «      j                  }	 |j                  dd¬«      ƒ d {  –—†  | j                  t
        «      j                  }|j                  «       ƒ d {  –—† dkD  ry | j                  t        «      j                  }|j                  «       ƒ d {  –—† dkD  r.	 |j                  «       ƒ d {  –—†  t        dd«      ƒ d {  –—†  y y 7 Œ¨# t        $ r Y y w xY w7 Œ‚7 ŒI7 Œ/7 Œ# t        $ r Y y w xY w­w)NrG   rH   rD   r   rt   ç      ð?)
r,   r   r-   rR   r0   r   r.   r   Úclickr   )r#   Ú	containerÚbarÚ
expand_btns       r   Ú_ensure_storage_section_visibler    sõ   è ø€ Ø—‘Ô4Ó5×;Ñ;€IðØ× Ñ  z¸5Ð ÓA×AÐAð �,‰,Ô-Ó
.×
4Ñ
4€CØ�Y‰Y‹[×˜1ÒØà—‘Ô9Ó:×@Ñ@€JØ×ÑÓ× !Ò#ð	Ø×"Ñ"Ó$×$Ð$Ü˜c 3Ó'×'Ñ'ð $ð 	BùÜò Ùðúð øð  øà$øØ'ùÜò 	Ùð	üs�   ‚ D
£C$ ¹C"ºC$ ¾2D
Á0C3Á1:D
Â+C5Â,D
Â4C; ÃC7ÃC; ÃC9ÃC; Ã D
Ã"C$ Ã$	C0Ã-D
Ã/C0Ã0D
Ã5D
Ã7C; Ã9C; Ã;	DÄD
ÄDÄD
c              ƒ   ó8  K  — t        | «      ƒ d {  –—†  | j                  t        «      j                  }|j	                  «       ƒ d {  –—† dk(  rU| j                  t
        «      j                  }|j	                  «       ƒ d {  –—† dkD  r|j                  d«      j                  }|j	                  «       ƒ d {  –—† dk(  ry |j                  d«      ƒ d {  –—† xs d}t        j                  |«      }|sy t        |j                  d«      «      S 7 Œþ7 ŒÉ7 Œ‘7 Œ]7 ŒB­w)Nr   z[class*="barTeamUsage"]Ústyler&   r   )r  r,   r   r-   r.   r   r/   ÚSTORAGE_BAR_WIDTH_PATTERNr   r¶   r   )r#   r  Úprogressr  r!   s        r   Ú_read_storage_percent_from_barr  .  sæ   è ø€ Ü
)¨$Ó
/×/Ð/à
�,‰,Ô-Ó
.×
4Ñ
4€CØ�Y‰Y‹[×˜AÒØ—<‘<Ô >Ó?×EÑEˆØ—‘Ó!×! AÒ%Ø×"Ñ"Ð#<Ó=×CÑCˆCØ�Y‰Y‹[×˜AÒØà×#Ñ# GÓ,×,Ò2°€EÜ%×,Ñ,¨UÓ3€EÙØÜ�—‘˜Q“Ó Ð ð 0øð øà!øàøð -úsW   ‚D‘D’6DÁDÁ	9DÂDÂ5DÂ8DÂ9DÃDÃ;DÄDÄDÄDÄDc              ƒ   ó  K  — t        | «      ƒ d {  –—†  t        t        fD ]  }| j                  |«      j                  }|j                  «       ƒ d {  –—† dk(  rŒ:|j                  «       ƒ d {  –—† xs dj                  «       }|sŒgt        j                  |«      sŒ}|c S  | j                  t        «      j                  }|j                  «       ƒ d {  –—† dkD  rC|j                  «       ƒ d {  –—† xs dj                  «       }|rt        j                  |«      r|S | j                  d«      j                  «       ƒ d {  –—† }t        j                  |«      }|r|j                  d«      S dS 7 �Œa7 �Œ"7 �Œ	7 Œ¤7 Œ‹7 Œ;­w)Nr   r&   Úbody)r  r   r   r,   r-   r.   Ú
inner_textr*   ÚSTORAGE_USAGE_PATTERNr   r   r   )r#   ÚselectorÚlocÚtextr  Ú	body_textr!   s          r   Ú_read_storage_usage_textr  @  s<  è ø€ Ü
)¨$Ó
/×/Ð/ä*Ô,CÓDˆØ�l‰l˜8Ó$×*Ñ*ˆØ—‘“× Ò!ØØ—n‘nÓ&×&Ò,¨"×3Ñ3Ó5ˆÚÔ)×0Ñ0°Õ6ØŠKð Eð —‘Ô4Ó5×;Ñ;€IØ�_‰_Ó× Ò"Ø×*Ñ*Ó,×,Ò2°×9Ñ9Ó;ˆÙÔ)×0Ñ0°Ô6ØˆKà—l‘l 6Ó*×5Ñ5Ó7×7€IÜ!×(Ñ(¨Ó3€EÙ"ˆ5�;‰;�q‹>Ð*¨Ð*ð% 0ùð ùà&ùð
 øØ,øð 8úsw   ‚F‘E2’AFÁE5ÁFÁ/E8Á0FÂ	FÂ7FÃE;ÃFÃ1E=Ã2AFÅE?Å/FÅ5FÅ8FÅ;FÅ=FÅ?Fc              ƒ   óî  K  — t        |xs d«      j                  «       xs d}	 t        | |¬«      ƒ d{  –—†  t        | «      ƒ d{  –—† }t	        | «      ƒ d{  –—† }|rt
        j                  |«      nd}|�Tt        t        |«      «      }|r|j                  d«      nd}|r|j                  d«      nd}t        |||«      }	|r||	d<   |	S |r3|j                  «       \  }}}t        t        |«      ||«      }	|r||	d<   |	S |�r]|j                  «       �rL	 t        | |«      ƒ d{  –—†  t        | «      ƒ d{  –—† }
|
j                  d«      xs i }|j                  d«      }|j                  d	«      }|j                  d
«      }|�Ú|�Øt!        |t"        «      r)t        |«      j%                  d«      j%                  d«      n
t        |«      }t!        |t"        «      r)t        |«      j%                  d«      j%                  d«      n
t        |«      }|€-	 t        t        t#        |«      t#        |«      z  dz  «      «      }t        t        |xs d«      ||«      }	||	d<   |	S dddœ|rd|i¥S i ¥S 7 �Œ=7 �Œ-7 �Œ7 �ŒS7 �ŒC# t&        t(        t*        f$ r d}Y ŒVw xY w# t,        $ r}t/        d|› d|› �«       Y d}~ŒXd}~ww xY w# t,        $ r%}t/        d|› �«       dddœ|rd|ini ¥cY d}~S d}~ww xY w­w)zÙ
    Read Manage Storage usage for the given Hudl team (or the active team).
    Uses the library page storage widget (fast). Manage Library is only a
    fallback so team switches are not blocked by a 45s probe.
    r&   NrZ   r   é   r$   Ústorager   r  r  Ú0Ú.éd   r   z/Manage Library storage read soft-fail for team rÞ   F)r  r  z2Non-critical: failed to fetch Hudl storage usage: )rd   r*   ra   r  r  r  r   rµ   Úroundr   r	  Úgroupsr+   Ú_ensure_manage_library_pageÚ_parse_manage_library_pager´   r±   r¶   ÚrstripÚ	TypeErrorrT   ÚZeroDivisionErrorr0   r^   )r#   r$   rU   Úpercent_from_barr  r!   rÿ   r   r  r  Úparsedr!  ÚusedÚtotalÚused_sÚtotal_srm   s                    r   Úfetch_hudl_storage_usager2  V  sù  è ø€ ô ˆgŠm˜Ó
×
"Ñ
"Ó
$Ò
,¨€Cð9^Ü" 4°Ô5×5Ð5ä!?ÀÓ!E×EÐÜ-¨dÓ3×3ˆÙ6:Ô%×,Ñ,¨TÔ2ÀˆàÐ'Üœ%Ð 0Ó1Ó2ˆGÙ+0˜Ÿ™ Qœ°bˆJÙ,1˜%Ÿ+™+ aœ.°rˆKÜ0°¸*ÀkÓRˆFÙØ$'��yÑ!ØˆMáØ/4¯|©|«~Ñ,ˆJ˜ WÜ0´°W³¸zÈ;ÓWˆFÙØ$'��yÑ!ØˆMò �3—;‘;•=ðVÜ1°$¸Ó<×<Ð<Ü9¸$Ó?×?�Ø Ÿ*™* YÓ/Ò5°2�Ø—{‘{ <Ó0�ØŸ™ MÓ2�Ø!Ÿ+™+ oÓ6�ØÐ#¨Ð(9ô & d¬EÔ2ô ˜D›	×(Ñ(¨Ó-×4Ñ4°SÔ9ä  ›Yð ô & e¬UÔ3ô ˜E›
×)Ñ)¨#Ó.×5Ñ5°cÔ:ä  ›Zð ð
 �ð(Ü&)¬%´°t³¼uÀU»|Ñ1KÈsÑ0RÓ*SÓ&T˜Gô 9¼¸Wº\ÈÓ9JÈFÐT[Ó\�FØ(+�F˜9Ñ%Ø!�Mð #°%Ñ]ÑPS¸YÈÐ<LÐ]Ð]ÐY[Ð]Ð]ðk 	6ùàEùØ3ùð, =ùØ?úô& !*¬:Ô7HÐIò (Ø&'šGð(ûô
 ò VÜÐGÈÀuÈBÈsÈeÐT×UÑUûðVûô ò ^ÜÐBÀ3À%ÐHÔIØ"°%Ñ]ÑPS¸YÈÑ<LÐY[Ð]Õ]ûð^üs  ‚"K5¥K µI2¶K ÁI5Á	K ÁI8ÁA2K ÃK5Ã4K ÄK5ÄK ÄJ Ä(I;Ä)J Ä;I>Ä<CJ È,J É J É#K5É$
K É.K5É/K É1K5É2K É5K É8K É;J É>J ÊJÊJ ÊJÊJ Ê	KÊ&J<Ê7K Ê<KËK Ë	K2ËK-Ë'K2Ë(K5Ë-K2Ë2K5r‹   c              ƒ   ó(  ‡‡K  — t        | «      ƒ d{  –—†  t        | «      ƒ d{  –—†  dŠt        j                  «       Šˆˆfd„}| j	                  d|«       	 t        d|› �«       | j                  |d¬«      ƒ d{  –—†  	 t        j                  ‰j                  «       d¬«      ƒ d{  –—†  | j                  d|«       ‰rd
‰dœS dddœS 7 Œ»7 Œª7 ŒT7 Œ(# t        j                  $ r t        d	«       Y ŒFw xY w# | j                  d|«       w xY w­w)zm
    Navigates to the direct watch URL, intercepts HLS requests (.m3u8),
    and returns the stream URL.
    Nc              “   óv   •K  — | j                   }d|v r$‰j                  «       s|Š‰j                  «        y y y ­w)Nz.m3u8)r   Úis_setrê   )Úrequestr   ÚeventÚ
stream_urls     €€r   Úintercept_requestz/get_video_stream_url.<locals>.intercept_request¥  s4   øè ø€ à�k‰kˆà�c‰> %§,¡,¤.ØˆJØ�I‰I�Kð #1ˆ>ùs   ƒ69r6  zNavigating to watch URL: r=   r>   g      .@rq   z-Timeout waiting for HLS request interception.rä   )r�   r8  r€   z.Failed to capture HLS video stream URL (.m3u8)©r�   rƒ   )Ú	login_runr   ÚasyncioÚEventrì   r^   rQ   rR   ÚwaitÚTimeoutErrorrí   )r#   r‹   r9  r7  r8  s      @@r   Úget_video_stream_urlr@  š  s  ùè ø€ ô
 �D‹/×ÐÜ
˜DÓ
!×!Ð!à€JÜ�M‰M‹O€Eõð 	‡G�GˆIÐ(Ô)ð;ÜÐ)¨)¨Ð5Ô6Ø�i‰i˜	Ð.@ˆiÓA×AÐAð	CÜ×"Ñ" 5§:¡:£<¸Ô>×>Ð>ð
 	×Ñ˜YÐ(9Ô:áàØ$ñ
ð 	
ð ØGñ
ð 	
ðG øØ!øð" 	Bøð ?ùÜ×#Ñ#ò 	CÜÐAÖBð	Cûð 	×Ñ˜YÐ(9Õ:üsy   „D“C”D¦C§2DÁ$C; Á>CÁ?C; Â(C Â,CÂ-C Â1DÃDÃC; ÃC ÃC8Ã5C; Ã7C8Ã8C; Ã;DÄDc              ƒ   ó  K  — d}	 | j                  |«      j                  }|j                  «       ƒ d{  –—† dkD  rAt        d«       |j	                  «       ƒ d{  –—†  | j                  d«      ƒ d{  –—†  dddœS t        d	«       t        | j                  «      s,d
| j                  v rt        d«       t        | «      ƒ d{  –—†  dddœS 7 Œ™7 Œu7 Œ^7 Œ# t        $ r&}t        d|› �«       dt        |«      dœcY d}~S d}~ww xY w­w)zX
    Clicks the close/back button on Hudl's watch screen to go back to the library.
    z3xpath=(//section/div/div/div/div/div/div/button)[1]Nr   z'Found close/back button. Clicking it...r=   rä   z,Video player closed and returned to library.r:  z;Close/back button not found. Maybe already on library page.z/watchz%Fallback: navigating back to library.z*Already on library page or navigated back.z#Error during closing video player: r€   )r,   r-   r.   r^   r  Úwait_for_load_stater   r   ra   r0   rd   )r#   Úclose_btn_xpathÚbtnÚes       r   Úclose_video_playerrF  Ç  sõ   è ø€ ð L€Oð6Ø�l‰l˜?Ó+×1Ñ1ˆØ—‘“×˜qÒ ÜÐ;Ô<Ø—)‘)“+×ÐØ×*Ñ*Ð+=Ó>×>Ð>Ø"Ð/]Ñ^Ð^äÐOÔPÜ# D§H¡HÔ-°(¸d¿h¹hÑ2FÜÐ=Ô>Ü*¨4Ó0×0Ð0Ø"Ð/[Ñ\Ð\ð øàøØ>øð 1ùäò 6ÜÐ3°A°3Ð7Ô8Ø!¬c°!«fÑ5Õ5ûð6üs‡   ‚D†.C ´Cµ%C ÁCÁC Á3CÁ4C Á<DÁ=AC ÃCÃC ÃDÃC ÃC ÃC ÃC Ã	DÃD Ã:DÃ;DÄ DÄDÚvideo_indexc              ƒ   óÂ  K  — | j                   xs d}d|v st        |«      s¨t        | «      ƒ d{  –—† }|rd|› d�nt        | «      ƒ d{  –—† }|j	                  dd«      d   }t        d	|› �«       | j                  |d
¬«      ƒ d{  –—†  t        dd«      ƒ d{  –—†  t        | «      ƒ d{  –—†  t        | d¬«      ƒ d{  –—†  | j                  t        «      }	 |j                  «       ƒ d{  –—† }|dkD  r‚d|cxk  r|k  rwn nt|j                  |«      }|j                  «       ƒ d{  –—†  	 |j                  «       ƒ d{  –—† }|r| j!                  d|«      ƒ d{  –—†  t        dd«      ƒ d{  –—†  |S t        | j                   «      sDt        d«       t%        | «      ƒ d{  –—†  t        | «      ƒ d{  –—†  t'        | «      ƒ d{  –—†  	 |j(                  j+                  dd¬«      ƒ d{  –—†  |j                  «       ƒ d{  –—† }|dk  s||k\  rt#        d|› d|› �«      ‚|j                  |«      }|j                  «       ƒ d{  –—†  	 |j                  «       ƒ d{  –—† }|r| j!                  d|«      ƒ d{  –—†  t        dd«      ƒ d{  –—†  |S 7 �Œu7 �Œ]7 �Œ"7 �Œ7 �Œ7 �Œï7 �ŒÄ7 �Œ‹7 �Œu7 �Œ\# t"        $ r Y �Œfw xY w7 �Œ[# t"        $ r Y �Œcw xY w7 �Œ97 �Œ)7 �Œ7 Œõ# t"        $ rÃ t        d«       	 | j-                  d
¬«      ƒ d{  –—†7   t        dd«      ƒ d{  –—†7   t        | «      ƒ d{  –—†7   t        | j                   «      st'        | «      ƒ d{  –—†7   |j(                  j+                  dd¬«      ƒ d{  –—†7   n# t"        $ r}	t#        d|	› �«      ‚d}	~	ww xY wY �Œ¾w xY w7 �Œ¯7 �Œn7 �ŒX7 �Œ?# t"        $ r Y �ŒIw xY w7 �Œ>­w)zYHelper to locate a library video tile by index after ensuring we are on the library page.r&   úv=Nr:   r   Ú?r   r   z(locate_tile: opening clean library list r=   r>   r@   rA   é N  rÜ   ú?(el) => el.scrollIntoView({block: 'center', inline: 'nearest'})rv   rw   zNavigating to Library page...rG   éà.  rD   zClocate_tile: No library tiles found. Attempting reload and retry...é   é   rC   z%No library tiles found after reload: zVideo index z  is out of range. Total videos: )r   r   r8   rW   Úsplitr^   rQ   r   r   r{   r,   r   r.   ÚnthÚscroll_into_view_if_neededÚelement_handlerj   r0   r;  ra   r-   rR   rî   )
r#   rG  Úcurrent_urlrU   Úlist_urlr_   r.   ÚtileÚhandleÚ	retry_errs
             r   Úlocate_tilerY  Þ  sÊ  è ø€ ð —(‘(’.˜b€KØˆ{ÑÔ"2°;Ô"?Ü,¨TÓ2×2ˆñ ð /¨s¨e°8Ñ<ä+¨DÓ1×1ð 	ð
 —>‘> # qÓ)¨!Ñ,ˆÜÐ8¸¸
ÐCÔDØ�i‰i˜Ð-?ˆiÓ@×@Ð@Ü˜#˜sÓ#×#Ð#Ü Ó%×%Ð%Ü'¨¸Ô?×?Ð?à—<‘<¤Ó-€LðØ"×(Ñ(Ó*×*ˆØ�1Š9˜˜kÔ1¨EÕ1Ø×#Ñ# KÓ0ˆDØ×1Ñ1Ó3×3Ð3ðØ#×2Ñ2Ó4×4�ÙØŸ-™-ØYØó÷ ð ô ˜c 3Ó'×'Ð'ØˆKô ˜DŸH™HÔ%ÜÐ-Ô.Ü˜‹o×ÐÜ Ó%×%Ð%Ü" 4Ó(×(Ð(ðQØ× Ñ ×)Ñ)°
ÀEÐ)ÓJ×JÐJð ×$Ñ$Ó&×&€EØ�Q‚˜+¨Ò.Ü˜, { mÐ3SÐTYÐSZÐ[Ó\Ð\à×Ñ˜KÓ(€DØ
×
)Ñ
)Ó
+×+Ð+ðØ×*Ñ*Ó,×,ˆÙØ—-‘-ØQØó÷ ð ô �c˜3Ó
×ÐØ€KðK 3ùð 2ùð 	AùØ#ùØ%ùØ?ùð +ùð 4ùà4ùðúô ò Úðúà'úäò Úðúð
 	ùØ%ùØ(ùð 	KùÜò 
QÜÐSÔTð	QØ—+‘+Ð);�+Ó<×<Ñ<Ü˜a Ó#×#Ñ#Ü# DÓ)×)Ñ)Ü# D§H¡HÔ-Ü*¨4Ó0×0Ñ0Ø×$Ñ$×-Ñ-°JÈÐ-ÓN×NÒNøÜò 	QÜÐCÀIÀ;ÐOÓPÐPûð	Qúò Oð
Qúð 'ùð
 ,ùà,ùðúô ò Úðúàûs>  ‚.O°J%±OÁJ(Á=OÂ	J+Â
OÂJ.ÂOÂ0J1Â1OÃJ4ÃOÃ K Ã3J7Ã4;K Ä/J:Ä0K Ä5K ÅJ=Å	K Å$K Å%K Å)K Å8KÅ9K Å>/OÆ-K&Æ.OÇ K)ÇOÇK,ÇOÇ K1 Ç9K/Ç:K1 Ç>OÈO ÈAOÉOÉOÉO É.OÉ/O Ê
O	ÊO ÊOÊOÊOÊ(OÊ+OÊ.OÊ1OÊ4OÊ7K Ê:K Ê=K Ë K Ë	KËK ËKËK Ë	K#ËOË"K#Ë#OË)OË,OË/K1 Ë1N=ÌNÌLÌNÌ0L3Ì1NÍMÍ(NÍ-M0Í.%NÎNÎNÎN=Î	N6Î#N1Î1N6Î6N=Î9OÎ<N=Î=OÏOÏO Ï	O Ï	OÏOÏOÏOc              ƒ   óÈ  K  — t        | |«      ƒ d{  –—† }|j                  «       ƒ d{  –—†  t        dd«      ƒ d{  –—†  |j                  d«      }|j	                  dd¬«      ƒ d{  –—†  |j                  «       ƒ d{  –—†  t        dd«      ƒ d{  –—†  | j                  d«      j                  d	«      j                  }|j	                  dd¬«      ƒ d{  –—†  |j                  «       ƒ d{  –—†  d
}| j                  |«      }|j	                  dd¬«      ƒ d{  –—†  |j                  «       ƒ d{  –—† }| j                  d«      }d}	t        g d¢«      D ]€  \  }
}|j                  d|
dz   › d�«      }|j                  «       ƒ d{  –—† dkD  sŒ:|j                  d«      ƒ d{  –—† }|j                  d«      ƒ d{  –—† xs d}|dk(  s	d|v sd|v sŒ~|}	 n d}d}| j                  |«      j                  }|j                  «       ƒ d{  –—† dkD  r|j                  «       ƒ d{  –—† }d||	|dœS 7 �Œ(7 �Œ7 �Œ7 �ŒÙ7 �ŒÄ7 �Œ³7 �Œq7 �Œ\7 �Œ17 �Œ7 ŒÈ7 Œ­7 Œ–7 ŒJ7 Œ1­w)zRClicks on tile menu and opens the details panel, returning title, type, and event.Nrt   r  úxpath=./div/div[2]rB   éˆ  rD   z*xpath=//button[@aria-label="Open Details"]úvisible=truez.xpath=//h4[@data-qa-id="details-module-title"]é@  z=xpath=//div[@data-qa-id="details-module-item-type-container"]ÚGame©r_  ÚScoutÚPracticezxpath=./div/div/div[r   Ú]r   zaria-checkedÚclassr&   ÚtrueÚcheckedÚactivezGxpath=//div[@data-qa-id="details-module-event-dropdown-select"]/div/divrä   )r�   r†   rŽ   r7  )rY  Úhoverr   r,   rR   r  r-   r  rï   r.   r/   )r#   rG  rV  Úmenu_btnÚopen_details_btnÚdetails_title_xpathÚ	title_locr†   Útype_containerÚselected_typeÚidxr‡   Údiv_locatorÚaria_checkedÚclsÚevent_select_xpathÚ	event_valÚ	event_locs                     r   Úopen_library_detailsrv  -  sV  è ø€ ä˜T ;Ó/×/€DØ
�*‰*‹,×ÐÜ
�c˜3Ó
×Ðà�|‰|Ð0Ó1€HØ
×
Ñ
 )°TÐ
Ó
:×:Ð:Ø
�.‰.Ó
×ÐÜ
�c˜3Ó
×Ðà—|‘|Ð$PÓQ×YÑYÐZhÓi×oÑoÐØ
×
#Ñ
#¨)¸TÐ
#Ó
B×BÐBØ
×
 Ñ
 Ó
"×"Ð"ð KÐØ—‘Ð0Ó1€IØ
×
Ñ
 9°dÐ
Ó
;×;Ð;à×&Ñ&Ó(×(€Eð —\‘\Ð"aÓb€NØ€MäÒ<Ö=‰	ˆˆTØ$×,Ñ,Ð/CÀCÈÁEÀ7È!Ð-LÓMˆØ×"Ñ"Ó$×$ qÓ(Ø!,×!:Ñ!:¸>Ó!J×JˆLØ#×1Ñ1°'Ó:×:Ò@¸bˆCØ˜vÒ%¨°cÑ)9¸XÈº_Ø $�Ùð >ð cÐØ€IØ—‘Ð/Ó0×6Ñ6€IØ�_‰_Ó× Ò"Ø#×.Ñ.Ó0×0ˆ	ð ØØØñ	ð ðQ 0ùØùØùð ;ùØùØùð CùØ"ùð
 <ùà(ùð %øØJøØ:øð øØ0ús  ‚I"’H:“I"ªH=«I"¾I ¿+I"Á*IÁ+I"ÂIÂI"ÂI	ÂAI"ÃIÃI"Ã3IÃ4-I"Ä!IÄ"I"Ä9IÄ:AI"ÆIÆI"ÆI"Æ,IÆ-I"ÇIÇI"Ç7I"ÈIÈI"È.I È/I"È=I"É I"ÉI"ÉI"É	I"ÉI"ÉI"ÉI"ÉI"ÉI"ÉI"ÉI"ÉI"É I"Ú
video_typec              ƒ   ó  K  — ddddœ}||vrt        d|› d�«      ‚||   }d|› d�}| j                  |«      }|j                  d	d
¬«      ƒ d{  –—†  |j                  «       ƒ d{  –—†  t	        dd«      ƒ d{  –—†  dd|› �dœS 7 Œ87 Œ"7 Œ­w)zAUpdates video type (Game, Scout, Practice) in open details panel.r   r   rN  r`  zInvalid video_type: z#. Must be Game, Scout, or Practice.zJxpath=//div[@data-qa-id="details-module-item-type-container"]/div/div/div[rc  rB   r\  rD   Nr  r\   rä   zUpdated video type to r:  )rT   r,   rR   r  r   )r#   rG  rw  Útype_mapÚdiv_idxÚoption_xpathÚ
option_locs          r   Úupdate_library_typer}  _  s¼   è ø€ ð  A°1Ñ5€HØ˜Ñ!ÜÐ/°
¨|Ð;^Ð_Ó`Ð`à�zÑ"€GØ_Ð`gÐ_hÐhiÐj€LØ—‘˜lÓ+€JØ
×
Ñ
 I°tÐ
Ó
<×<Ð<Ø
×
Ñ
Ó
×ÐÜ
�c˜3Ó
×Ðð Ø+¨J¨<Ð8ñð ð	 =øØøØús6   ‚ABÁBÁBÁ&B	Á'BÁ:BÁ;BÂ	BÂBc              ƒ   óø  K  — | j                  d«      j                  }|j                  dd¬«      ƒ d{  –—†  |j                  «       ƒ d{  –—†  | j                  d«      }|j                  dd¬«      ƒ d{  –—†  |j                  d«      }|j	                  «       ƒ d{  –—† }|dk(  r)|j                  d	«      }|j	                  «       ƒ d{  –—† }g }t        |«      D ]H  }|j                  |«      j                  «       ƒ d{  –—† }|j                  |j                  «       «       ŒJ | j                  j                  d
«      ƒ d{  –—†  t        dd«      ƒ d{  –—†  d|dœS 7 �Œ47 �Œ7 Œõ7 ŒÎ7 Œ¢7 Œm7 Œ+7 Œ­w)zUOpens the event dropdown, fetches options list, closes dropdown, and returns options.ú1xpath=//span[@aria-label="select open and close"]rB   r\  rD   Nú5xpath=//div[@class="u-select__menu css-1nmdiq5-menu"]ú:xpath=.//div[contains(@class, "option") or @role="option"]r   úxpath=./div/divÚEscapert   r  rä   )r�   Úoptions)r,   r-   rR   r  r.   ÚrangerQ  r  r¸   r*   ÚkeyboardÚpressr   )	r#   rG  Údropdown_arrowÚmenu_locÚoptions_locr.   r„  rù   Útxts	            r   Úget_library_event_optionsrŒ  s  s`  è ø€ à—\‘\Ð"UÓV×\Ñ\€NØ
×
!Ñ
!¨	¸4Ð
!Ó
@×@Ð@Ø
×
Ñ
Ó
 × Ð à�|‰|ÐSÓT€HØ
×
Ñ
 )°TÐ
Ó
:×:Ð:à×"Ñ"Ð#_Ó`€KØ×#Ñ#Ó%×%€EØ�‚zØ×&Ñ&Ð'8Ó9ˆØ!×'Ñ'Ó)×)ˆà€GÜ�5Ž\ˆØ—O‘O AÓ&×1Ñ1Ó3×3ˆØ�‰�s—y‘y“{Õ#ð ð
 �-‰-×
Ñ
˜hÓ
'×'Ð'Ü
�c˜3Ó
×Ðð Øñð ð+ AùØ ùð ;øð &øð *øð 4øð (øØúsŽ   ‚2E:´E(µE:ÁE+Á+E:Á8E.Á9(E:Â!E0Â"-E:ÃE2Ã6E:ÄE4ÄAE:Å
E6ÅE:ÅE8Å
E:Å+E:Å.E:Å0E:Å2E:Å4E:Å6E:Å8E:Ú
event_namec              ƒ   óº  K  — | j                  d«      j                  }|j                  dd¬«      ƒ d{  –—†  |j                  «       ƒ d{  –—†  | j                  d«      }|j                  dd¬«      ƒ d{  –—†  |j                  d«      }|j	                  «       ƒ d{  –—† }|dk(  r)|j                  d	«      }|j	                  «       ƒ d{  –—† }d
}t        |«      D ]h  }|j                  |«      }	|	j                  «       ƒ d{  –—† }
|
j                  «       |j                  «       k(  sŒN|	j                  «       ƒ d{  –—†  d} n |s dj                  |j                  «       «      }t        |«      D ]s  }|j                  |«      }	|	j                  «       ƒ d{  –—† }
dj                  |
j                  «       «      }||k(  s	||v s||v sŒY|	j                  «       ƒ d{  –—†  d} n |s2| j                  j                  d«      ƒ d{  –—†  t        d|› d�«      ‚t        dd«      ƒ d{  –—†  dd|› �dœS 7 �Œ
7 �Œõ7 �ŒÌ7 �Œ¦7 �Œ{7 �ŒE7 �Œ7 Œ³7 Œp7 ŒH7 Œ'­w)z8Selects an event from the dropdown inside details panel.r  rB   r\  rD   Nr€  r�  r   r‚  FTÚ rƒ  zEvent option 'z' not found.r  r\   rä   zUpdated event to r:  )r,   r-   rR   r  r.   r…  rQ  r  r*   ÚjoinrP  r†  r‡  r0   r   )r#   rG  r�  rˆ  r‰  rŠ  r.   Úclickedrù   Úoptr‹  Únorm_targetÚnorm_txts                r   Úupdate_library_eventr•  ‘  s9  è ø€ à—\‘\Ð"UÓV×\Ñ\€NØ
×
!Ñ
!¨	¸4Ð
!Ó
@×@Ð@Ø
×
Ñ
Ó
 × Ð à�|‰|ÐSÓT€HØ
×
Ñ
 )°TÐ
Ó
:×:Ð:ð ×"Ñ"Ð#_Ó`€KØ×#Ñ#Ó%×%€EØ�‚zØ×&Ñ&Ð'8Ó9ˆØ!×'Ñ'Ó)×)ˆà€Gä�5Ž\ˆØ�o‰o˜aÓ ˆØ—N‘NÓ$×$ˆØ�9‰9‹;˜*×*Ñ*Ó,Ó,Ø—)‘)“+×ÐØˆGÙð ñ Ø—h‘h˜z×/Ñ/Ó1Ó2ˆÜ�u–ˆAØ—/‘/ !Ó$ˆCØŸ™Ó(×(ˆCØ—x‘x §	¡	£Ó,ˆHØ˜hÒ&¨+¸Ñ*AÀXÐQ\ÒE\Ø—i‘i“k×!Ð!Ø�Ùð ñ Ø�m‰m×!Ñ! (Ó+×+Ð+Ü˜.¨¨°LÐAÓBÐBä
�c˜3Ó
×ÐàØ& z lÐ3ñð ðQ AùØ ùð ;ùð &ùð *ùð %ùàùð )øð "øð
 	,øð  úsÐ   ‚2I´H>µIÁIÁ+IÁ8IÁ9(IÂ!IÂ"-IÃI
Ã8IÄIÄ	%IÄ/IÅIÅAIÆIÆ 0IÇIÇ$IÇ%)IÈIÈ"IÈ1IÈ2IÉIÉIÉIÉ
IÉIÉIÉIÉIÉIÉIc              ƒ   óÐ   K  — | j                  d«      }|j                  dd¬«      ƒ d{  –—†  |j                  «       ƒ d{  –—†  t        dd«      ƒ d{  –—†  dd	d
œS 7 Œ57 Œ7 Œ­w)zCloses details panel.z9xpath=//button[@data-qa-id="details-module-close-button"]rB   r\  rD   Nrt   r  rä   zDetails panel closedr:  )r,   rR   r  r   )r#   Ú	close_btns     r   Úclose_library_detailsr˜  Ã  sk   è ø€ à—‘ÐXÓY€IØ
×
Ñ
 9°dÐ
Ó
;×;Ð;Ø
�/‰/Ó
×ÐÜ
�c˜3Ó
×ÐàØ)ñð ð <øØøØús3   ‚(A&ªA «A&ÁA"ÁA&ÁA$Á
A&Á"A&Á$A&FÚnew_nameÚcancelc              ƒ   óR  K  — t        | |«      ƒ d{  –—† }|j                  «       ƒ d{  –—†  t        dd«      ƒ d{  –—†  |j                  d«      }|j	                  dd¬«      ƒ d{  –—†  |j                  «       ƒ d{  –—†  t        dd«      ƒ d{  –—†  | j                  d«      j                  d	«      j                  }|j	                  dd¬«      ƒ d{  –—†  |j                  «       ƒ d{  –—†  | j                  d
«      }|j	                  dd¬«      ƒ d{  –—†  |j                  «       ƒ d{  –—† }|r/| j                  d«      }	|	j                  «       ƒ d{  –—†  d|ddœS |�d||k7  r_|j                  |«      ƒ d{  –—†  | j                  d«      }
|
j                  «       ƒ d{  –—†  t        dd«      ƒ d{  –—†  d|d|› �dœS | j                  d«      }	|	j                  «       ƒ d{  –—†  d|dœS 7 �Œï7 �ŒÚ7 �ŒÉ7 �Œ 7 �Œ‹7 �Œz7 �Œ87 �Œ#7 Œù7 Œã7 Œº7 Œ–7 Œo7 Œ]7 Œ-­w)zEOpens rename inline panel, enters new name and clicks save or cancel.Nrt   r  r[  rB   r\  rD   z$xpath=//button[@aria-label="Rename"]r]  z6xpath=//input[@data-qa-id="library-item-rename-input"]z8xpath=//button[@data-qa-id="library-item-rename-cancel"]rä   zRenaming cancelled)r�   r‡   rƒ   z6xpath=//button[@data-qa-id="library-item-rename-save"]r\   zRenamed to )r�   r‡   )	rY  rh  r   r,   rR   r  r-   Úinput_valueÚfill)r#   rG  r™  rš  rV  ri  Úrename_menu_btnÚ	input_locÚcurrent_valueÚ
cancel_btnÚsave_btns              r   Úrename_library_videor£  Ï  s   è ø€ ä˜T ;Ó/×/€DØ
�*‰*‹,×ÐÜ
�c˜3Ó
×Ðà�|‰|Ð0Ó1€HØ
×
Ñ
 )°TÐ
Ó
:×:Ð:Ø
�.‰.Ó
×ÐÜ
�c˜3Ó
×Ðà—l‘lÐ#IÓJ×RÑRÐSaÓb×hÑh€OØ
×
"Ñ
"¨¸DÐ
"Ó
A×AÐAØ
×
Ñ
Ó
!×!Ð!à—‘ÐUÓV€IØ
×
Ñ
 9°dÐ
Ó
;×;Ð;à#×/Ñ/Ó1×1€MáØ—\‘\Ð"\Ó]ˆ
Ø×ÑÓ × Ð àØ!Ø+ñ
ð 	
ð Ð ¨MÒ 9Ø�n‰n˜XÓ&×&Ð&Ø—<‘<Ð XÓYˆØ�n‰nÓ×ÐÜ˜#˜sÓ#×#Ð#àØØ$ X JÐ/ñ
ð 	
ð —‘ÐXÓY€JØ
×
Ñ
Ó
×ÐàØñð ðS 0ùØùØùð ;ùØùØùð BùØ!ùð <øà1øð 	!øð 	'øàøØ#øð ús  ‚H'’H“H'ªH«H'¾H¿+H'Á*H
Á+H'ÂHÂH'ÂHÂAH'ÃHÃH'Ã3HÃ4+H'ÄHÄ H'Ä7HÄ8*H'Å"HÅ#%H'ÆHÆ	(H'Æ1H!Æ2H'ÇH#Ç1H'Ç7H%Ç8
H'ÈH'ÈH'È
H'ÈH'ÈH'ÈH'ÈH'ÈH'ÈH'ÈH'ÈH'È!H'È#H'È%H'Úoptionc              ƒ   óî  K  — t        | |«      ƒ d{  –—† }|j                  «       ƒ d{  –—†  t        dd«      ƒ d{  –—†  |j                  d«      }|j	                  dd¬«      ƒ d{  –—†  |j                  «       ƒ d{  –—†  t        dd«      ƒ d{  –—†  | j                  d«      j                  d	«      j                  }|j	                  dd¬«      ƒ d{  –—†  |j                  «       ƒ d{  –—†  t        dd
«      ƒ d{  –—†  |r.| j                  d«      }|j                  «       ƒ d{  –—†  dddœS |dk(  r| j                  d«      }n| j                  d«      }|j                  «       ƒ d{  –—†  t        dd«      ƒ d{  –—†  | j                  d«      }	|	j                  «       ƒ d{  –—†  t        dd
«      ƒ d{  –—†  dddœS 7 �ŒÀ7 �Œ«7 �Œš7 �Œq7 �Œ\7 �ŒK7 �Œ	7 Œó7 Œá7 Œ¸7 Œu7 Œc7 Œ<7 Œ*­w)zOOpens Email Download Link popup, selects option, and triggers email or cancels.Nrt   r  r[  rB   r\  rD   z1xpath=//button[@aria-label="Email Download Link"]r]  r\   z3xpath=//button[@data-qa-id="download-modal-cancel"]rä   z!Email download request cancelled.r:  Ú
video_onlyzJxpath=//label[@data-qa-id="download-modal-radio-options-video-only-label"]zOxpath=//label[@data-qa-id="download-modal-radio-options-video-and-stats-label"]z5xpath=//button[@data-qa-id="download-modal-download"]z)Email download request successfully sent.)rY  rh  r   r,   rR   r  r-   )
r#   rG  r¤  rš  rV  ri  Ú	email_btnr¡  Ú	radio_locÚdownload_btns
             r   Úemail_download_linkrª     sÑ  è ø€ ä˜T ;Ó/×/€DØ
�*‰*‹,×ÐÜ
�c˜3Ó
×Ðà�|‰|Ð0Ó1€HØ
×
Ñ
 )°TÐ
Ó
:×:Ð:Ø
�.‰.Ó
×ÐÜ
�c˜3Ó
×Ðà—‘ÐPÓQ×YÑYÐZhÓi×oÑo€IØ
×
Ñ
 9°dÐ
Ó
;×;Ð;Ø
�/‰/Ó
×ÐÜ
�c˜3Ó
×Ðñ Ø—\‘\Ð"WÓXˆ
Ø×ÑÓ × Ð ØÐ+NÑOÐOð �ÒØ—L‘LÐ!mÓn‰	à—L‘LÐ!rÓsˆ	à
�/‰/Ó
×ÐÜ
�c˜3Ó
×Ðà—<‘<Ð WÓX€LØ
×
Ñ
Ó
×ÐÜ
�c˜3Ó
×ÐàÐ'RÑSÐSðC 0ùØùØùð ;ùØùØùð <ùØøØøð
 	!øð øØøð øØús÷   ‚G5’G“G5ªG«G5¾G¿+G5Á*GÁ+G5ÂGÂG5ÂG!ÂAG5ÃG$ÃG5Ã3G'Ã4G5ÄG)Ä*G5Ä2G+Ä3AG5Å7G-Å8G5ÆG/Æ(G5Æ4G1Æ5G5ÇG3Ç	
G5ÇG5ÇG5ÇG5ÇG5Ç!G5Ç$G5Ç'G5Ç)G5Ç+G5Ç-G5Ç/G5Ç1G5Ç3G5c              ƒ   ób   K  — t        | |«      ƒ d{  –—† }t        | |«      ƒ d{  –—† S 7 Œ7 Œ­w)z]Clicks download button, intercepts file, saves it to browser_data/downloads, returns details.N)rY  Ú_download_from_library_tile)r#   rG  rV  s      r   Údownload_library_videor­  &  s.   è ø€ ä˜T ;Ó/×/€DÜ,¨T°4Ó8×8Ð8ð 0øØ8ús   ‚/’+“/¦-§/­/Úvaluec                 óH   — | xs dj                  «       j                  d«      S )uH   Canonical Hudl v id for API use â€” trailing base64 `=` padding removed.r&   Ú=)r*   r)  )r®  s    r   Ú_canonical_hudl_vr±  ,  s    € àŠK�R×ÑÓ ×'Ñ'¨Ó,Ð,r   c                 óx   — t        | «      }|r|j                  «       r|S dt        |«      dz  z
  dz  }|d|z  z   S )z:Restore base64 padding so Hudl analyze URLs still resolve.é   r°  )r±  r+   r¹   )r®  ÚsÚpads      r   Ú_pad_hudl_v_for_urlr¶  1  sA   € ä˜%Ó €AÙ�—	‘	”ØˆØ”�A“˜‘
Ñ˜qÑ
 €CØ��c‘	‰?Ðr   c                 ó8  — ddl m}m}m}  || xs dj	                  «       «      }|sy|j                  «       sd|vrd|vrd|vrt        |«      S d|v sd|v sd|v r¶|j                  d«      r |d|› �«      }n'd|v r	 ||«      }n |d	|j                  d
«      › �«      } ||j                  d¬«      }|j                  d«      xs g }|rGt        |d   «      j	                  «       r+t         |t        |d   «      j	                  «       «      «      S t        |«      S )zINormalize a Hudl `v` id (no trailing `=`); also accepts a full watch URL.r   ©Úparse_qsÚunquoteÚurlparser&   z://rI  rJ  rK   rO   zhttps://app.hudl.com/?z?&T©Úkeep_blank_valuesrá   )Úurllib.parser¹  rº  r»  r*   r+   r±  rS   ÚlstripÚqueryr´   rd   )r®  r¹  rº  r»  rb   r-  ÚqsÚvalss           r   Ú_extract_hudl_vrÃ  :  s  € ç8Ñ8á
�5’;˜B×%Ñ%Ó'Ó
(€CÙØà
‡{�{„}˜ cÑ)¨d¸#©oÀ#ÈSÁ.Ü  Ó%Ð%Ø��|�t˜s‘{ c¨S¡jØ�>‰>˜#ÔÙÐ 4°S°EÐ:Ó;‰FØ�c‰\Ù˜c“]‰FáÐ 6°s·z±zÀ$Ó7GÐ6HÐIÓJˆFÙ�f—l‘l°dÔ;ˆØ�v‰v�c‹{Ò ˜bˆÙ”C˜˜Q™“L×&Ñ&Ô(Ü$¡W¬S°°a±«\×-?Ñ-?Ó-AÓ%BÓCÐCÜ˜SÓ!Ð!r   rá   c                 óŒ  ‡‡	— ddl m}mŠ	m} t	         ‰	|xs dj                  «       «      «      Š‰sy| xs dj                  «       }|sy ‰	|«      }d‰› �|v sd‰› �|v ryd‰› d�|v sd‰› d�|v ry	  | ||«      j                  d¬«      }|j                  d	«      xs g }t        ˆˆ	fd
„|D «       «      S # t        $ r Y yw xY w)Nr   r¸  r&   FrI  Tr°  r¼  rá   c              3   ót   •K  — | ]/  }t         ‰t        |«      j                  «       «      «      ‰k(  –— Œ1 y ­wrÉ   )r±  rd   r*   )rË   ÚxÚtargetrº  s     €€r   rÎ   z#_watch_url_has_v.<locals>.<genexpr>d  s.   øè ø€ ÐVÑQUÈAÔ$¡W¬S°«V¯\©\«^Ó%<Ó=ÀÕGÑQUùs   ƒ58)
r¾  r¹  rº  r»  r±  r*   rÀ  r´   rÖ   r0   )
r‹   rá   r¹  r»  r   Údecoded_urlrÁ  rÂ  rÇ  rº  s
           @@r   Ú_watch_url_has_vrÉ  R  sÛ   ù€ ß8Ñ8ä™w¨ª¨R§¡Ó'8Ó9Ó:€FÙØØŠ?˜×
!Ñ
!Ó
#€CÙØÙ˜#“,€Kà	ˆFˆ8€}˜Ñ  F 8˜}°Ñ;ØØ	ˆFˆ8�1€~˜Ñ " V H¨A °+Ñ =ØðÙ‘h˜s“m×)Ñ)¸TÔBˆØ�v‰v�c‹{Ò ˜bˆÜÔVÑQUÓVÓVÐVøÜò Ùðús   Á3AB7 Â7	CÃCÚ	raw_inputc                 ó:  — ddl m}m}m}m}m} |xs | xs dj                  «       }t        |j                  d«      rd|v s| n|«      }	t        |	«      }
|j                  d«      rzd|v rv ||«      } ||j                  d¬«      }|
g|d<   g }|j                  «       D ]  \  }}|D ]  }|j                  ||f«       Œ Œ!  ||j                   ||d	¬
«      ¬«      «      S |xs dj                  «       xs t        |«      xs d} ||
d	¬
«      }|rd|› d|› �S d|› �S )zGBuild a Hudl analyze watch URL for a given v id (pads base64 for Hudl).r   )ÚquoteÚ	urlencoder»  r¹  Ú
urlunparser&   rJ   rI  Tr¼  rá   z=+/)Úsafe)rÀ  r:   z/analyze?v=z*https://app.hudl.com/watch/team/analyze?v=)r¾  rÌ  rÍ  r»  r¹  rÎ  r*   rÃ  rS   r¶  rÀ  r©   r¸   Ú_replacer"   )rá   r$   rÊ  rÌ  rÍ  r»  r¹  rÎ  rb   Úv_canonÚ	v_for_urlr-  rÁ  ÚflatrÂ   rÂ  rÃ   rU   Úqvs                      r   Ú_build_watch_url_for_vrÕ  i  s!  € çMÕMàÒ˜Ò˜R×
&Ñ
&Ó
(€CÜ¨¯©°vÔ(>À4È3Á;™aÐUXÓY€GÜ# GÓ,€Ià
‡~�~�fÔ $¨#¡+á˜#“ˆÙ�f—l‘l°dÔ;ˆØ�+ˆˆ3‰ØˆØŸ™ž‰IˆC�Û�Ø—‘˜S #˜JÕ'ñ ð $ñ ˜&Ÿ/™/±	¸$ÀUÔ0K˜/ÓLÓMÐMàŠ=�b×
Ñ
Ó
!Ò
EÔ%:¸3Ó%?Ò
EÀ2€CÙ	ˆy˜uÔ	%€BÙ
Ø1°#°°kÀ"ÀÐFÐFØ7¸°tÐ<Ð<r   c              ƒ   óä	  ‡ ‡K  — ddl m} ddl}ddlm} ‰ j                  «       rt        d«      ‚	 ‰ j                  j                  d«      ƒ d{  –—†  t        dd«      ƒ d{  –—†  ‰j                  «       ƒ d{  –—†  	 ‰j                  «       ƒ d{  –—† }|r‰ j                  d	|«      ƒ d{  –—†  ‰j                  «       ƒ d{  –—†  t        d
d«      ƒ d{  –—†  d5ˆfd„}d6dt        dt        fˆ ˆfd„} |«       ƒ d{  –—†   |d¬«      ƒ d{  –—† }|j!                  d«      s„	 ‰ j                  j                  d«      ƒ d{  –—†  t        dd«      ƒ d{  –—†  ‰j                  «       ƒ d{  –—†  t        dd«      ƒ d{  –—†   |«       ƒ d{  –—†   |d¬«      ƒ d{  –—† }|j!                  d«      s(t#        d|j!                  d«      › �«       t        d«      ‚t#        d|j!                  d«      › d�«       ‰ j%                  d¬«      4 ƒd{  –—† }	 |d¬«      ƒ d{  –—† }
|
j!                  d«      st        d«      ‚	 ‰ j'                  d«      j'                  d«      j(                  }|j+                  d d!¬"«      ƒ d{  –—†  ‰ j'                  d#«      j'                  d«      j(                  }|j-                  «       ƒ d{  –—† dkD  r,|j/                  «       ƒ d{  –—†  t        d$d%«      ƒ d{  –—†  t#        d&«       |j/                  «       ƒ d{  –—†  ddd«      ƒd{  –—†  	j0                  ƒ d{  –—† }|j2                  xs d'}t#        d(|› d)�«        ||«      j4                  xs d*}t7        |«      d+kD  rd*}t9        |j;                  «       «      }||z  }|j=                  dd¬,«       |d-|› �z  }|j?                  t9        |«      «      ƒ d{  –—†  |jA                  «       st        d.«      ‚|jC                  «       jD                  }d/|› d0|› �}t#        d1|› d2|› d3�«       dt9        |«      |||d|d4œS 7 �Œ # t        $ r Y �Œ
w xY w7 �Œÿ7 �Œê7 �ŒÔ7 �Œ»# t        $ r Y �ŒÅw xY w7 �Œ¶7 �Œ¥7 �Œ7 �Œq7 �Œ?# t        $ r Y �ŒIw xY w7 �Œ>7 �Œ)7 �Œ7 �Œ7 �Œþ7 �Œ�7 �Œ�7 �Œ"7 �Œã7 �ŒË7 �Œº7 �Œš# t        $ r Y �Œ¤w xY w7 �Œ�# 1 ƒd{  –—†7  sw Y   �Œ®xY w7 �Œ¢7 �Œ­w)7zO
    Hudl tile Download click, then write file under media/{id}/video.mp4.
    r   ©ÚPathN©Ú
MEDIA_ROOTú*Browser page is closed. Retry the request.rƒ  çš™™™™™É?çš™™™™™Ù?rL  rt   r  r   c               “   ó®  •K  — ‰j                  d«      j                  } | j                  «       ƒ d {  –—† dk(  r‰j                  d«      j                  } | j                  «       ƒ d {  –—† dk(  r‰j                  d«      } | j                  dd¬«      ƒ d {  –—†  | j	                  d¬	«      ƒ d {  –—†  t        d
d«      ƒ d {  –—†  y 7 Œ˜7 Œd7 Œ77 Œ7 Œ­w)Nú6xpath=.//button[contains(@data-qa-id, "-action-list")]r   z‹xpath=.//button[@aria-haspopup="menu" or contains(@aria-label,"More") or contains(@aria-label,"Action") or contains(@aria-label,"options")]r[  rB   r^  rD   T)Úforceru   rs   )r,   r-   r.   rR   r  r   )ri  rV  s    €r   Ú_open_tile_menuz4_download_from_library_tile.<locals>._open_tile_menu£  sÃ   øè ø€ Ø—<‘<Ð XÓY×_Ñ_ˆØ—‘Ó!×! QÒ&Ø—|‘|ðXó÷ ‰eð ð —‘Ó!×! QÒ&Ø—|‘|Ð$8Ó9ˆHØ×Ñ i¸ÐÓ>×>Ð>Ø�n‰n 4ˆnÓ(×(Ð(Ü˜#˜sÓ#×#Ñ#ð "øð
 "øà>øØ(øØ#úsW   ƒ/C²C³5CÁ(CÁ).CÂCÂCÂ1CÂ2CÃCÃCÃCÃCÃCÃCFÚdo_clickc              “   ó�   •K  — ‰j                  «       ƒ d {  –—† xs dddddœ}‰j                  d| |dœ«      ƒ d {  –—† S 7 Œ*7 Œ­w)Nr   )rÆ  ÚyÚwidthÚheighta  ({ doClick, tileBox }) => {
              const nodes = Array.from(
                document.querySelectorAll('button, [role="menuitem"], a[role="menuitem"], div[role="menuitem"]')
              );

              const isVisible = (el) => {
                if (!el || el.offsetParent === null) return false;
                const r = el.getBoundingClientRect();
                return r.width > 0 && r.height > 0;
              };

              const parts = (el) => {
                const aria = (el.getAttribute('aria-label') || '').replace(/\s+/g, ' ').trim();
                const text = (el.textContent || '').replace(/\s+/g, ' ').trim();
                return { aria, text, combo: (aria + ' ' + text).replace(/\s+/g, ' ').trim() };
              };

              const isEmail = (p) => /email/i.test(p.aria + ' ' + p.text);
              const isDownload = (p) => {
                if (isEmail(p)) return false;
                if (/^download$/i.test(p.aria) || /^download$/i.test(p.text)) return true;
                if (/^(download\s+)+download$/i.test(p.combo)) return true;
                return false;
              };

              const inTileMenu = (el) => {
                const root =
                  el.closest('[role="menu"], [data-radix-menu-content], [class*="Menu" i], [class*="Popover" i], [class*="Dropdown" i]')
                  || el.parentElement;
                if (!root) return false;
                const t = (root.innerText || '').toLowerCase();
                return (
                  t.includes('open details') ||
                  t.includes('rename') ||
                  t.includes('delete') ||
                  t.includes('share') ||
                  t.includes('email download')
                );
              };

              const distToTile = (el) => {
                const r = el.getBoundingClientRect();
                const cx = r.left + r.width / 2;
                const cy = r.top + r.height / 2;
                const tx = tileBox.x + tileBox.width / 2;
                const ty = tileBox.y + tileBox.height / 2;
                const dx = cx - tx;
                const dy = cy - ty;
                return Math.sqrt(dx * dx + dy * dy);
              };

              const candidates = nodes
                .filter((el) => isVisible(el) && isDownload(parts(el)))
                .map((el) => ({ el, dist: distToTile(el), menu: inTileMenu(el) }))
                .sort((a, b) => {
                  // Prefer menu items near this tile
                  if (a.menu !== b.menu) return a.menu ? -1 : 1;
                  return a.dist - b.dist;
                });

              const target = candidates[0] && candidates[0].el;
              if (!target) {
                return {
                  ok: false,
                  labels: nodes.filter(isVisible).map((el) => parts(el).combo).filter(Boolean).slice(0, 40),
                };
              }
              if (doClick) {
                target.scrollIntoView({ block: 'nearest' });
                target.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
                target.dispatchEvent(new MouseEvent('mouseup', { bubbles: true }));
                target.click();
              }
              return { ok: true, label: parts(target).combo, dist: candidates[0].dist };
            })ÚdoClickÚtileBox)Úbounding_boxrj   )râ  Útile_boxr#   rV  s     €€r   Ú_find_downloadz3_download_from_library_tile.<locals>._find_download°  s^   øè ø€ Ø×*Ñ*Ó,×,ÒY°a¸aÈ!ÐWXÑ0YˆØ—]‘]ðJðV !¨XÑ6óYM
÷ M
ð M	
ð -øðM
ús   ƒA—A˜%A½A¾AÁA)râ  rä   çffffffæ?z#download_tile: menu labels visible=Úlabelsz~Hudl Download button not found after opening the tile menu. Make sure the Library list is open (not the player) and try again.zdownload_tile: found Download (r�   u   ) â€” saving into media/ ...iÀ'	 rq   Tz.Hudl Download button disappeared before click.z,button[data-qa-id="download-modal-download"]r]  rB   é   rD   zAlabel[data-qa-id="download-modal-radio-options-video-only-label"]rv   rw   z:download_tile: confirming Hudl download modal (video only)ú	video.mp4z!download_tile: download started (z), writing to media/ ...ú.mp4é
   ©ÚparentsÚexist_okrÑ   z9Hudl download finished but file was not written to media/úmedia/ú/videoz!download_tile: saved directly to ú (z bytes)©r�   Ú
local_pathÚpathÚfilenameÚmedia_idÚsaved_to_mediaÚ	file_sizeræ   )F)#ÚpathlibrØ  ÚuuidÚapp.services.media_servicerÚ  Ú	is_closedr0   r†  r‡  r   rR  rS  rj   rh  r·   r³   r´   r^   Úexpect_downloadr,   r-   rR   r.   r  r®  Úsuggested_filenameÚsuffixr¹   rd   Úuuid4ÚmkdirÚsave_asÚis_fileÚstatÚst_size)r#   rV  rØ  r   rÚ  rW  rá  rë  ÚfoundÚdownload_infor‘  Úmodal_dlÚonlyÚdownloadrû  Úextrü  Údest_dirÚdestÚsizeÚrels   ``                   r   r¬  r¬  ƒ  s™  ùè ø€ õ Ûå5à‡~�~ÔÜÐDÓEÐEðØ�m‰m×!Ñ! (Ó+×+Ð+ô �c˜3Ó
×Ðà
×
)Ñ
)Ó
+×+Ð+ðØ×*Ñ*Ó,×,ˆÙØ—-‘-ØQØó÷ ð ð �*‰*‹,×ÐÜ
�c˜3Ó
×Ðõ$ñO
¤tð O
¼ö O
ñb Ó
×ÐÙ ¨%Ô0×0€EØ�9‰9�TŒ?ð	Ø—-‘-×%Ñ% hÓ/×/Ð/ô ˜#˜sÓ#×#Ð#Ø�j‰j‹l×ÐÜ˜#˜sÓ#×#Ð#ÙÓ×ÐÙ$¨eÔ4×4ˆà�9‰9�TŒ?ÜÐ3°E·I±I¸hÓ4GÐ3HÐIÔJÜðQó
ð 	
ô
 
Ð+¨E¯I©I°gÓ,>Ð+?Ð?[Ð
\Ô]à×#Ñ#¨FÐ#×3Ô3°}Ù&°Ô5×5ˆØ�{‰{˜4Ô ÜÐLÓMÐMð	Ø—|‘|Ø>óç‰g�nÓ%§e¡eð ð ×#Ñ#¨)¸TÐ#ÓB×BÐBØ—<‘<ØSóç‰g�nÓ%§e¡eð ð —Z‘Z“\×! AÒ%Ø—j‘j“l×"Ð"Ü! # sÓ+×+Ð+ÜÐNÔOØ—.‘.Ó"×"Ð"÷! 4×3ð( #×(Ñ(×(€HØ×*Ñ*Ò9¨k€HÜ	Ð-¨h¨ZÐ7OÐ
PÔQá
ˆx‹.×
Ñ
Ò
) 6€CÜ
ˆ3ƒx�"‚}Øˆä�4—:‘:“<Ó €HØ˜HÑ$€HØ‡N�N˜4¨$€NÔ/Ø˜˜c˜U�mÑ#€Dà
×
Ñ
œ3˜t›9Ó
%×%Ð%Ø�<‰<Œ>ÜÐSÓTÐTà�9‰9‹;×Ñ€DØ�8�*˜F 3 %Ð
(€CÜ	Ð-¨c¨U°"°T°F¸'Ð
BÔCð Ü˜$“iØØØØØñð ð_ 	,úÜò Úðúàùà+ùà,ùðúô ò ÚðúàùØùð@ ùØ0ùð 0úÜò 	Úð	úà#ùØùØ#ùØùØ4ùð 4ùØ5ùð Cùð "ùØ"ùØ+ùà"úÜò 	Úð	úð# 4ù×3×3Ò3úð( )ùð &ûsa  „,S0±Q ÁQÁQ ÁS0Á#Q"Á$S0Á;Q%Á<S0ÂQ. ÂQ(ÂQ. Â0Q+Â1Q. Â5S0ÃQ>Ã	S0ÃRÃ(S0ÄRÄS0ÄRÄS0Ä-R ÅR
ÅR ÅS0ÅRÅ S0Å7R Å8S0ÆR#ÆS0ÆR&ÆS0Æ+R)Æ,A1S0ÈR,ÈS0È!SÈ.R/È/ SÉA SÊR2ÊASËR5ËSË-R8Ë.SÌR;Ì"SÌ$R>Ì%SÌ)S0Ì4SÌ5S0ÍS*Í	BS0Ï(S-Ï)A&S0ÑQ Ñ	QÑS0ÑQÑS0Ñ%S0Ñ(Q. Ñ+Q. Ñ.	Q;Ñ7S0Ñ:Q;Ñ;S0ÒS0ÒS0ÒS0Ò
R Ò	RÒS0ÒRÒS0Ò S0Ò#S0Ò&S0Ò)S0Ò,S0Ò/SÒ2SÒ5SÒ8SÒ;SÒ>SÓ	SÓ
SÓSÓSÓS0ÓS'ÓSÓS'Ó"	S0Ó-S0Úv_cleanc              ƒ   óü  K  — | j                  t        «      }	 |j                  j                  dd¬«      ƒ d{  –—†  t        d«      D ]ì  }|j                  «       ƒ d{  –—† }t        |«      D ]Œ  }|j                  |«      }d}	 |j                  d	«      j                  }|j                  «       ƒ d{  –—† d
kD  r|j                  d«      ƒ d{  –—† xs d}t        ||«      sŒwt        d|› d|› �«       |c c S  | j                  j                  d
d«      ƒ d{  –—†  t        dd«      ƒ d{  –—†  Œî y7 Œÿ# t        $ r t        | d¬«      ƒ d{  –—†7   Y �Œw xY w7 �Œ7 Œ¯7 Œ•# t        $ r Y �Œw xY w7 ŒZ7 ŒH­w)z]Find 0-based LIBRARY_TILE index whose href contains this v (same tiles as download-to-media).rG   rC   rD   NrK  rÜ   é   r&   úa[href]r   rI   z_resolve_dom_index_for_v: v=z -> tile index i¤  çffffffÖ?rì  )r,   r   r-   rR   r0   r{   r…  r.   rQ  r/   rÉ  r^   ry   rz   r   )	r#   r  r_   Ú_r.   rù   rV  rI   Úas	            r   Ú_resolve_dom_index_for_vr  K  se  è ø€ à—<‘<¤Ó-€Lð@Ø× Ñ ×)Ñ)°
ÀEÐ)ÓJ×JÐJô �2ŽYˆØ"×(Ñ(Ó*×*ˆÜ�u–ˆAØ×#Ñ# AÓ&ˆDØˆDðØ—L‘L Ó+×1Ñ1�ØŸ™›—? QÒ&Ø"#§/¡/°&Ó"9×9Ò@¸b�Dô    gÕ.ÜÐ4°W°I¸_ÈQÈCÐPÔQØ”ð ð �j‰j×Ñ˜q $Ó'×'Ð'Ü˜$ Ó$×$Ñ$ð ð  ð) 	KùÜò @Ü'¨¸Ô?×?Ô?ð@úð +ùð #øØ9ùÜò Úðúð
 	(øØ$ús»   ‚E<™ D; ¹D9ºD; ¾!E<ÁE!Á %E<Â.E(Â4E$Â5E(ÃE&ÃE(ÃE<Ã&7E<ÄE8ÄE<Ä1E:Ä2E<Ä9D; Ä;EÅEÅEÅE<ÅEÅE<Å$E(Å&E(Å(	E5Å1E<Å4E5Å5E<Å:E<c              ƒ   ó¦  K  — ddl m} t        |«      }|st        d«      ‚t	        |«      }t        |«      xs t        | «      ƒ d{  –—† }|xs t        |||¬«      }ddlm	}  || «      ƒ d{  –—†  t        | «      ƒ d{  –—†  t        d|› �«       | j                  |d¬	«      ƒ d{  –—†  t        d
d«      ƒ d{  –—†  t        | «      ƒ d{  –—†  t        | |d¬«      ƒ d{  –—†  t        | ||«      ƒ d{  –—† }	|	�|	d|fS dD ]Y  }
	 | j                   j#                  d|
«      ƒ d{  –—†  t        dd«      ƒ d{  –—†  t        | ||«      ƒ d{  –—† }	|	€ŒT|	d|fc S  |�rXd|› d�}|| j$                  xs dv�r?t        d|› �«       | j                  |d¬	«      ƒ d{  –—†  t        d
d«      ƒ d{  –—†  t        | «      ƒ d{  –—†  t'        | d¬«      ƒ d{  –—†  t        | ||«      ƒ d{  –—† }	|	�|	d|fS t)        d«      D ]X  }| j                   j#                  dd«      ƒ d{  –—†  t        dd«      ƒ d{  –—†  t        | ||«      ƒ d{  –—† }	|	€ŒS|	d|fc S  | j                  |d¬	«      ƒ d{  –—†  t        dd«      ƒ d{  –—†  t        | «      ƒ d{  –—†  t        | |d¬«      ƒ d{  –—†  | j$                  xs d}t+        ||«      rt        d|› d�«       dd|fS t        d|› d�«      ‚7 �Œº7 �Œ•7 �Œ…7 �Œ_7 �ŒN7 �Œ>7 �Œ+7 �Œ7 �Œë# t        $ r Y  �ŒÂw xY w7 �Œë7 �ŒÙ7 �ŒŒ7 �Œ{7 �Œk7 �ŒY7 �ŒG7 �Œ7 Œÿ7 Œì7 ŒÈ7 Œ¶7 Œ¥7 Œ‘­w) z™
    Open analyze?v=<id> and locate THAT video's card/link.
    Does not hard-require [data-qa-id=tile-library-item] (often missing on player view).
    r   )rº  zMissing Hudl v idN©r$   rÊ  r}   zOpening library video by v at r=   r>   r@   rA   rÛ   rÜ   )é@  iÀùÿÿr   r   r   r   r  rì  r:   r   r&   z/locate_tile_by_v: falling back to library list rK  é   i  ç333333ó?r\   rC   z locate_tile_by_v: no card for v=u&   ; page URL has v â€” use page downloadéÿÿÿÿzNo library card found for v=zC. Confirm this video belongs to the active Hudl team and try again.)r¾  rº  rÃ  r0   r¶  r"   r8   rÕ  rç   r~   r   r^   rQ   r   Ú_wait_for_analyze_readyÚ_find_card_for_vry   rz   r   r{   r…  rÉ  )r#   rá   r‹   rº  r  Úv_paddedrU   Ú
target_urlr~   ÚmatchedÚ	directionrU  r  Úcurrents                 r   Úlocate_tile_by_vr+  f  sl  è ø€ õ
 %ä˜aÓ €GÙÜÐ+Ó,Ð,Ü" 7Ó+€Hä
 	Ó
*Ò
RÔ4LÈTÓ4R×.R€CØÒWÔ4°WÀcÐUVÔW€Jå>á
˜dÓ
#×#Ð#Ü
˜DÓ
!×!Ð!ä	Ð*¨:¨,Ð
7Ô8Ø
�)‰)�JÐ+=ˆ)Ó
>×>Ð>Ü
�c˜3Ó
×ÐÜ
˜DÓ
!×!Ð!Ü
! $¨¸EÔ
B×BÐBä$ T¨7°HÓ=×=€GØÐØ˜˜:Ð%Ð%ó ;ˆ	ð	Ø—*‘*×"Ñ" 1 iÓ0×0Ð0ô ˜$ Ó$×$Ð$Ü(¨¨w¸ÓA×AˆØÑØ˜A˜zÐ)Ò)ð ;ò Ø5°c°U¸(ÐCˆØ˜DŸH™HšN¨Ò+ÜÐCÀHÀ:ÐNÔOØ—)‘)˜HÐ1C�)ÓD×DÐDÜ˜c 3Ó'×'Ð'Ü# DÓ)×)Ð)Ü+¨D¸UÔC×CÐCÜ,¨T°7¸HÓE×EˆGØÐ"Ø  :Ð-Ð-Ü˜1–X�Ø—j‘j×&Ñ& q¨$Ó/×/Ð/Ü! $¨Ó,×,Ð,Ü 0°°wÀÓ I×I�ØÑ&Ø" A zÐ1Ò1ð ð —)‘)˜JÐ3E�)ÓF×FÐFÜ˜c 3Ó'×'Ð'Ü# DÓ)×)Ð)Ü)¨$°ÀEÔJ×JÐJà�h‰hŠn˜"€GÜ˜ Ô)äÐ0°°	Ð9_Ð`ÔaØ�R˜Ð#Ð#ä
Ø
& w ið 0Lð 	Lóð ðw /Sùð
 $ùØ!ùð ?ùØùØ!ùØBùà=ùð 1úÜò 	Ûð	úà$ùØAùð EùØ'ùØ)ùØCùØEùð 0ùØ,øØIøð GøØ'øØ)øØJúsÊ  ‚AMÁLÁ'MÁ/LÁ0MÂLÂ(MÂ+L
Â,MÂ?LÃ MÃLÃMÃ(LÃ)MÃ=LÃ>MÄLÄ.LÄ/LÄ3MÅL-ÅMÅL0ÅMÅAMÆ'L3Æ(MÆ;L6Æ<MÇL9ÇMÇ#L<Ç$MÇ8L?Ç98MÈ1MÈ2MÉMÉMÉMÉMÉ"MÊ M	ÊMÊMÊMÊ'MÊ(MÊ=MÊ>AMÌMÌMÌ
MÌMÌMÌMÌMÌLÌ	L*Ì%MÌ)L*Ì*MÌ0MÌ3MÌ6MÌ9MÌ<MÌ?MÍMÍMÍMÍ	MÍMÍMÍMc              ƒ   óÖ   K  — t        |«      }	 | j                  d||¬«      ƒ d{  –—†  t	        dd«      ƒ d{  –—†  y7 Œ# t        $ r}t        d|› d�«       Y d}~Œ4d}~ww xY w7 Œ,­w)zSWait until analyze SPA shows tiles, player, download control, or a link for this v.aƒ  (v) => {
              if (!v) return false;
              if (document.querySelector(`a[href*="${v}"]`)) return true;
              if (document.querySelector('[data-qa-id="tile-library-item"]')) return true;
              if (document.querySelector('[data-qa-id*="library-item" i], [data-qa-id*="cutup" i]')) return true;
              if (document.querySelector('button[aria-label*="Download" i]')) return true;
              if (document.querySelector('video, [class*="Player"], [class*="player"]')) return true;
              if (/\d+\s*Items?/i.test(document.body?.innerText || '')) return true;
              return false;
            })ÚargrF   Nz'_wait_for_analyze_ready: soft timeout (Ú)rw   r"  )r±  rx   r0   r^   r   )r#   rá   ro   r  rm   s        r   r$  r$  ³  sƒ   è ø€ ä Ó"€Gð@Ø×$Ñ$ð	ð Øð %ó 
÷ 	
ð 	
ô  �c˜3Ó
×Ñð!	
ùô ò @ÜÐ7¸°u¸AÐ>×?Ñ?ûð@úàúsG   ‚A)�A ¦A §A «A)ºA'»A)Á A Á	A$ÁAÁA)ÁA$Á$A)r&  c           
   ƒ   óÂ  K  — ||xs t        |«      fD �cg c]  }|sŒ|‘Œ	 }}g d¢}|D ]¬  }| j                  d|› d�«      j                  }|j                  «       ƒ d{  –—† dk(  rŒ>	 |j	                  «       ƒ d{  –—†  dD ]N  }| j                  d|› dt        |«      › d	�«      j                  }	|	j                  «       ƒ d{  –—† dkD  sŒJ|	c c S  |c S  |D ]¿  }
| j                  |
«      }	 |j                  «       ƒ d{  –—† }t        t        |d
«      «      D ]{  }|j                  |«      }d}	 |j                  d«      j                  }|j                  «       ƒ d{  –—† dkD  r|j                  d«      ƒ d{  –—† xs d}t        ||«      sŒw|c c S  ŒÁ yc c}w 7 �ŒD7 �Œ*# t
        $ r Y �Œ4w xY w7 Œô7 Œ·# t
        $ r Y Œíw xY w7 Œi7 ŒO# t
        $ r Y Œ¾w xY w­w)zRFind a card/link for this v using flexible selectors (not only tile-library-item).)ú [data-qa-id="tile-library-item"]ú[data-qa-id*="tile" i]ú[data-qa-id*="library-item" i]ú[data-qa-id*="cutup" i]z[data-qa-id*="media-card" i]z[data-qa-id*="video-card" i]z[data-qa-id*="grid-item" i]ú	a[href*="ú"]Nr   )r0  r2  r3  r1  zxpath=//a[contains(@href, "z")]/ancestor::*[z][1]éP   r&   r  rI   )r¶  r,   r-   r.   rR  r0   Ú_xpath_attr_containsr…  ÚminrQ  r/   rÉ  )r#   r  r&  rÆ  ÚneedlesÚcard_selectorsÚneedleÚlinkÚcsÚancÚselÚtilesr.   rù   rV  rI   r  s                    r   r%  r%  Ê  sï  è ø€ à" HÒ$LÔ0CÀGÓ0LÑMÓSÑM�QÒQRŠqÐM€GÐSò€Nó ˆØ�|‰|˜i¨ x¨rÐ2Ó3×9Ñ9ˆØ—‘“× Ò"Øð	Ø×1Ñ1Ó3×3Ð3ó
ˆBð —,‘,Ø-¨f¨XÐ5EÔFZÐ[]ÓF^ÐE_Ð_cÐdóç‰eð ð —Y‘Y“[×  1Ó$Ø”
ð
ð Šð+ ó. ˆØ—‘˜SÓ!ˆð	ØŸ+™+›-×'ˆEô ”s˜5 "“~Ö&ˆAØ—9‘9˜Q“<ˆDØˆDðØ—L‘L Ó+×1Ñ1�ØŸ™›—? QÒ&Ø"#§/¡/°&Ó"9×9Ò@¸b�Dô    gÕ.Ø”ñ 'ð ð" ùòg Tð ùð 4úÜò 	Úð	úð !øð (ùÜò 	Ùð	úð #øØ9ùÜò Ùðüsí   ‚G–FžF¢=GÁF#Á GÁ)F)Á<F&Á=F)ÂAGÃF9ÃGÃ"GÃ0F=ÄF;ÄF=Ä+GÄ4.GÅ"GÅ#GÅ>GÅ?GÆGÆGÆ&F)Æ)	F6Æ2GÆ5F6Æ6GÆ;F=Æ=	G	ÇGÇG	Ç	GÇGÇGÇ	GÇGÇGÇGÚcss_likec                 óÔ   — d| v r2d| vr.| j                  dd«      d   j                  dd«      d   }d|› d�S d| v r.| j                  dd«      d   j                  dd«      d   }d	|› d
�S y)zEMap a simple data-qa-id CSS selector to an XPath attribute predicate.zdata-qa-id="z*=r   Ú"r   z@data-qa-id="zdata-qa-id*=zdata-qa-id*="zcontains(@data-qa-id, "ú")ztrue())rP  )rA  rÃ   s     r   r7  r7    sŠ   € ð ˜Ñ! d°(Ñ&:Ø�n‰n˜^¨QÓ/°Ñ2×8Ñ8¸¸aÓ@ÀÑCˆØ˜s˜e 1Ð%Ð%Ø˜Ñ!Ø�n‰n˜_¨aÓ0°Ñ3×9Ñ9¸#¸qÓAÀ!ÑDˆØ(¨¨¨RÐ0Ð0Ør   c           	   ƒ   óŽ	  ‡‡K  — ddl mŠ ddlŠt        |«      }t	        dd«      ƒ d{  –—†  t        | «      ƒ d{  –—†  t        | j                  xs d|«      s>| j                  d|› d�«      j                  «       ƒ d{  –—† dkD  }|st        d	|› �«      ‚d
t        fˆˆfd„}| j                  d«      j                  d«      }|j                  «       ƒ d{  –—† dkD  rp	 | j                  d¬«      4 ƒd{  –—† }|j                  j                  «       ƒ d{  –—†  ddd«      ƒd{  –—†   |j                  ƒ d{  –—† «      ƒ d{  –—† S g d¢}|D �]e  }	| j                  |	«      j                  d«      }
|
j                  «       ƒ d{  –—† }t#        t%        |d«      «      D �]  }	 |
j'                  |«      j                  «       ƒ d{  –—†  t	        dd«      ƒ d{  –—†  | j                  d«      j                  d«      j                  }|j                  «       ƒ d{  –—† dk(  r%	 | j(                  j+                  d«      ƒ d{  –—†  Œª	 | j                  d¬«      4 ƒd{  –—† }|j                  «       ƒ d{  –—†  ddd«      ƒd{  –—†   |j                  ƒ d{  –—† «      ƒ d{  –—† c c S  �Œh |t-        |«      fD ]®  }| j                  d|› d�«      j                  }|j                  «       ƒ d{  –—† dk(  rŒ>	 |j/                  «       ƒ d{  –—†  |j1                  «       ƒ d{  –—†  t	        dd«      ƒ d{  –—†  t3        | |«      ƒ d{  –—† }|j5                  d«      dk(  r|c S Œ° t        d|› d�«      ‚7 �Œd7 �ŒT7 �Œ7 �Œ½7 �Œ¡7 �Œ‚7 �Œu# 1 ƒd{  –—†7  sw Y   �Œ†xY w7 �Œx7 �Œo# t        $ r}t!        d|› �«       Y d}~�ŒŠd}~ww xY w7 �ŒU7 �Œ7 �Œ# t        $ r Y �ŒPw xY w7 �Œ×7 �Œ³# t        $ r Y �Œ½w xY w7 �Œ©7 �Œ”7 �Œ‡# 1 ƒd{  –—†7  sw Y   �Œ˜xY w7 �ŒŠ7 �Œ�# t        $ rN}t!        d|› �«       	 | j(                  j+                  d«      ƒ d{  –—†7   n# t        $ r Y nw xY wY d}~�Œád}~ww xY w7 �Œ�7 �Œu7 �Œ`7 �ŒO7 �Œ># t        $ r}t!        d|› �«       Y d}~�Œðd}~ww xY w­w)z“
    When analyze?v= is open, download the currently opened video via page controls
    (works even when tile-library-item is not in the DOM).
    r   r×  Nr  gÍÌÌÌÌÌü?r&   r4  r5  zPage is not open on v=r   c              “   óè  •K  — ddl m} | j                  xs d} ‰|«      j                  xs d}t	        |«      dkD  rd}t        ‰	j                  «       «      }||z  }|j                  dd¬«       |d|› �z  }| j                  t        |«      «      ƒ d {  –—†  d	|› d
|› �}t        d|› �«       dt        |«      |||d|j                  «       r|j                  «       j                  dœS d dœS 7 ŒY­w)Nr   rÙ  rï  rð  rñ  Trò  rÑ   rõ  rö  z(_download_for_open_v: saved directly to rä   rø  )r  rÚ  r  r  r¹   rd   r  r  r  r^   r	  r
  r  )
r  rÚ  rû  r  rü  r  r  r  rØ  r   s
           €€r   Ú_save_downloadz,_download_for_open_v.<locals>._save_download!  sÿ   øè ø€ Ý9à×.Ñ.Ò=°+ˆÙ�8‹n×#Ñ#Ò- vˆÜˆs‹8�bŠ=ØˆCÜ�t—z‘z“|Ó$ˆØ Ñ(ˆØ�‰˜t¨dˆÔ3Ø˜E # ˜-Ñ'ˆØ×Ñœs 4›yÓ)×)Ð)Ø�x�j  s eÐ,ˆÜÐ8¸¸Ð>Ô?àÜ˜d›)ØØ Ø Ø"Ø04·±´˜Ÿ™›×,Ñ,ñ
ð 	
ð EIñ
ð 	
ð 	*ús   ƒBC2ÂC0ÂAC2zabutton[aria-label="Download"], button[aria-label*="Download" i], button[data-qa-id*="download" i]r]  ià“ rq   z-_download_for_open_v direct download failed: )z$button[aria-label*="More options" i]z$button[aria-label*="More actions" i]zbutton[aria-label="More"]zbutton[aria-label*="Actions" i]z#button[data-qa-id*="action-list" i]zbutton[aria-haspopup="menu"]zbutton[aria-label*="menu" i]é   rÝ  rr   zybutton[aria-label="Download"], button[aria-label*="Download" i], xpath=//button[contains(normalize-space(.), "Download")]rƒ  z+_download_for_open_v menu download failed: r�   rä   z1_download_for_open_v link hover download failed: z	Opened v=z{ but could not find a Download control for that video. Make sure Hudl session is logged in and this video allows downloads.)rÿ  rØ  r   rÃ  r   r   rÉ  r   r,   r.   r0   r³   r  r-   r  r®  r^   r…  r8  rQ  r†  r‡  r¶  rR  rh  r¬  r´   )r#   rá   r  Úhas_linkrG  Údirectr  rm   Úmenu_selectorsr?  ÚbtnsÚnrù   r©  r;  r<  ÚresrØ  r   s                    @@r   Ú_download_for_open_vrO    s]  ùè ø€ õ
 Ûä˜aÓ €Gä
�c˜3Ó
×ÐÜ
˜DÓ
!×!Ð!ä˜DŸH™HšN¨¨GÔ4àŸ™¨	°'°¸"Ð&=Ó>×DÑDÓF×FÈÑJˆÙÜÐ4°W°IÐ>Ó?Ð?ð
¬$ö 
ð2 �\‰\ð	+ó÷ �gˆnÓð ð �\‰\‹^×˜aÒð	IØ×+Ñ+°FÐ+×;Ô;¸}Ø—l‘l×(Ñ(Ó*×*Ð*÷ <×;á'¨m×.AÑ.A×(AÓB×BÐBò
€Nô ˆØ�|‰|˜CÓ ×(Ñ(¨Ó8ˆØ—*‘*“,×ˆÜ”s˜1˜a“y×!ˆAðØ—h‘h˜q“k×'Ñ'Ó)×)Ð)Ü! # sÓ+×+Ð+ð  Ÿ<™<ðKó÷ ‰g�nÓ%§e¡eð ð "×'Ñ'Ó)×)¨QÒ.ðØŸ-™-×-Ñ-¨hÓ7×7Ð7ð ð	Ø×/Ñ/¸Ð/×?Ô?À=Ø&×,Ñ,Ó.×.Ð.÷ @×?á+°-×2EÑ2E×,EÓF×FÔFò) "ð ð@ Ô/°Ó8Ó9ˆØ�|‰|˜i¨ x¨rÐ2Ó3×9Ñ9ˆØ—‘“× Ò"Øð	MØ×1Ñ1Ó3×3Ð3Ø—*‘*“,×ÐÜ˜c 3Ó'×'Ð'Ü3°D¸$Ó?×?ˆCØ�w‰w�xÓ  DÒ(Ø’
ð )ð :ô Ø
�G�9ð Oð 	Oóð ðM  ùØ!ùð GùðB ùà;ùØ*ùð <ù×;×;Ò;úà(AùÐBúÜò 	IÜÐAÀ#ÀÐG×HÒHûð	Iúð ùð *ùØ+úÜò Úðúð *ùð 8úÜ ò Úðúð @ùØ.ùð @ù×?×?Ò?úà,EùÐFúÜò ÜÐCÀCÀ5ÐIÔJðØŸ-™-×-Ñ-¨hÓ7×7Ò7øÜ ò Ùðÿüð	úð ùð 4ùØùØ'ùØ?úô ò 	MÜÐEÀcÀUÐK×LÒLûð	MüsÎ  „%S©NªS¼N½ASÂNÂASÃNÃSÃ"N> Ã8NÃ9N> Ã<N"ÄNÄN"ÄN> Ä*NÄ+N> Å N8ÅN> ÅN;ÅN> Å>SÆO#ÆSÆ-"O,ÇO&ÇO,Ç#O)Ç$O,Ç(=SÈ%O<È&SÈ.PÉO?ÉPÉSÉP7É)P
É*P7É-PÊP
ÊPÊP7ÊPÊP7Ê'P1Ê(P7Ê3P4Ê4P7Ê7ASÌRÌSÌR ÌRÌ R Ì7RÌ8R ÍRÍR ÍRÍ R Í9SÎSÎSÎSÎN> ÎN"ÎN> Î"N5Î(N+Î)N5Î0	N> Î;N> Î>	O ÏOÏSÏO Ï SÏ&O,Ï)O,Ï,	O9Ï5SÏ8O9Ï9SÏ?PÐ	PÐSÐPÐSÐP7ÐPÐP7ÐP.Ð!P$Ð"P.Ð)	P7Ð4P7Ð7	RÑ R	ÑQ4Ñ-Q0
Ñ.Q4Ñ3R	Ñ4	R 	Ñ=R	Ñ?R 	Ò R	ÒSÒ	RÒSÒR ÒR ÒR ÒR Ò 	SÒ)R=Ò7SÒ=SÓSc              ƒ   óÒ
  ‡‡K  — ddl mŠ  ‰t        «      j                  «       j                  d   Šdt
        dt
        fˆˆfd„}|xs dj                  «       }|rt        |«      nd}|�€ö|sd	d
dœS t        |«      xs t        | «      ƒ d{  –—† }t        |||¬«      }d}	 ddlm}	 | j                  «       rt        d«      ‚ |	| «      ƒ d{  –—†  t        | «      ƒ d{  –—†  |rd|› d�n|}
t!        d|
› �«       | j#                  |
d¬«      ƒ d{  –—†  t%        dd«      ƒ d{  –—†  t        | «      ƒ d{  –—†  t'        | d¬«      ƒ d{  –—†  t)        | |«      ƒ d{  –—† }|€�t!        d«       t+        | |¬«      ƒ d{  –—† }t-        |j/                  d«      xs g «      D ]N  \  }}t1        |j/                  d«      xs d«      }t3        ||«      sŒ1|}|xs |}|j/                  d«      xs |} n |€	d	d|› d�dœS t!        d|› d |› �«       t5        | |«      ƒ d{  –—† }|j/                  d#«      d$k7  r|S  ||«      }||d%<   ||d&<   ||d<   |r||d<   |S 	 ddlm}	  |	| «      ƒ d{  –—†  t        | «      ƒ d{  –—†  t        | «      ƒ d{  –—† }|rd|› d�nt7        | «      ƒ d{  –—† }
|
j9                  d'd(«      d   }
t!        d)|› d*|
› �«       | j#                  |
d¬«      ƒ d{  –—†  t%        dd«      ƒ d{  –—†  t        | «      ƒ d{  –—†  t'        | d¬«      ƒ d{  –—†  d}d}	 t;        | |«      ƒ d{  –—† }|j=                  d,«      j>                  }|jA                  «       ƒ d{  –—† dkD  r|jC                  d-«      ƒ d{  –—† xs d}|j=                  d.«      j>                  }|jA                  «       ƒ d{  –—† dkD  r.|jE                  «       ƒ d{  –—† xs dj                  «       xs d}|rt        |«      }|r|}t5        | |«      ƒ d{  –—† }|j/                  d#«      d$k7  r|S  ||«      }||d%<   |xs d|d&<   |r||d<   n3|r1t        |«      xs t        | «      ƒ d{  –—† }t        |||¬«      |d<   |r||d<   |S 7 �Œ7 �ŒÏ7 �Œ¿7 �Œ�7 �Œ~7 �Œn7 �Œ\7 �ŒK7 �Œ,7 �Œ�# t        $ r)}t!        d!|› d"|› �«       d	t1        |«      dœcY d}~S d}~ww xY w7 �Œ{7 �Œk7 �Œ[7 �ŒC7 �Œ7 �Œô7 �Œä7 �ŒÒ# t        $ r}t!        d+|› �«       Y d}~�Œíd}~ww xY w7 �Œá7 �Œ±7 �Œ˜7 �Œd7 �ŒL# t        $ r}t!        d/|› d"|› �«       Y d}~�ŒCd}~ww xY w7 �Œ<7 Œè­w)0z·
    Same end result as /library/video/download-to-media:
    resolve the video, run the proven tile Download, save under media/{id}/.
    Accepts either video_index or Hudl `v`.
    r   r×  r   rN  r   c                 ó  •— | j                  d«      }|rP	 t         ‰|«      j                  «       j                  ‰j                  «       «      «      }|| d<   t        |«      | d<   | S # t        $ r t        |«      }Y Œ+w xY w)Nrù  rú  )r´   rd   ÚresolveÚrelative_tor0   )rN  Úlocalr  rØ  Úbackend_roots      €€r   Ú_with_pathsz2download_library_video_by_ref.<locals>._with_paths’  s|   ø€ Ø—‘˜Ó%ˆÙð!Ü™$˜u›+×-Ñ-Ó/×;Ñ;¸L×<PÑ<PÓ<RÓSÓT�ð ˆC�‰KÜ # E£
ˆC�ÑØˆ
øô	 ò !Ü˜%“j’ð!ús   –<A' Á'A>Á=A>r&   Nr€   zProvide either video_index or vr:  r  r}   rÛ  r:   r   z=download_by_ref: opening library (same as download-to-media) r=   r>   r@   rA   rK  rÜ   uB   download_by_ref: DOM href miss â€” matching via get_library_videosrZ   r‚   r‹   r†   zNo library video found with v=z;. Open Library in the app and confirm this video is listed.z;download_by_ref: using proven download_library_video index=z v=z+download_library_video_by_ref failed for v=rÞ   r�   rä   rG  rá   rJ  r   z+download_by_ref: opening library for index=r�  z6download_by_ref: library prepare failed (continuing): r  rI   z"[data-qa-id*="title" i], h3, h2, pz4download_by_ref: could not read tile meta for index=)#rÿ  rØ  Ú__file__rR  ró  r³   r*   rÃ  r"   r8   rÕ  rç   r~   r  r0   r   r^   rQ   r   r{   r  rý   rï   r´   rd   rÉ  r­  rW   rP  rY  r,   r-   r.   r/   r  )r#   rG  rá   rV  Úv_rawr  rU   r‹   r†   r~   rU  Úmatched_indexÚlibrù   ÚvidÚwrN  rm   rV  r  Útitle_elÚv_from_tilerØ  rU  s                         @@r   Údownload_library_video_by_refr_  „  s¡  ùè ø€ õ áœ“>×)Ñ)Ó+×3Ñ3°AÑ6€Lð	œð 	¤$ö 	ð ŠW�"�O‰OÓ€EÙ(-Œo˜eÔ$°2€Gð ÑÙØ%Ð2SÑTÐTä# EÓ*ÒRÔ4LÈTÓ4R×.RˆÜ*¨7¸CÈ5ÔQˆ	Øˆð/	<ÝFà�~‰~ÔÜÐ LÓMÐMá% dÓ+×+Ð+Ü# DÓ)×)Ð)áKNÐ9¸#¸¸hÑGÐT]ˆHÜÐQÐRZÐQ[Ð\Ô]Ø—)‘)˜HÐ1C�)ÓD×DÐDÜ˜c 3Ó'×'Ð'Ü# DÓ)×)Ð)Ü+¨D¸UÔC×CÐCä":¸4ÀÓ"I×IˆMð Ð$ÜÐZÔ[Ü.¨t¸SÔA×A�Ü'¨¯©°Ó(9Ò(?¸RÖ@‘F�A�sÜ˜CŸG™G KÓ0Ò6°BÓ7�AÜ'¨¨7Õ3Ø()˜Ø$%¢N¨˜	Ø #§¡¨Ó 0Ò 9°E˜Ùð Að Ð$à%à8¸¸	ð BTð Tñð ô ðØ&˜ s¨7¨)ð5ôô
 /¨t°]ÓC×CˆCð
 �7‰7�8Ó Ò$ØˆJá˜#ÓˆØ*ˆˆMÑØˆˆC‰Ø$ˆˆKÑÙØ ˆC�‰LØˆ
ð
NÝBá! $Ó'×'Ð'Ü Ó%×%Ð%Ü,¨TÓ2×2ˆñ ð /¨s¨e°8Ñ<ä+¨DÓ1×1ð 	ð
 —>‘> # qÓ)¨!Ñ,ˆÜÐ;¸K¸=ÈÈ(ÈÐTÔUØ�i‰i˜Ð-?ˆiÓ@×@Ð@Ü˜#˜sÓ#×#Ð#Ü Ó%×%Ð%Ü'¨¸Ô?×?Ð?ð
 €IØ€Eð[Ü   {Ó3×3ˆØ�L‰L˜Ó#×)Ñ)ˆØ—‘“�?˜QÒØ Ÿ™¨vÓ6×6Ò?¸4ˆIØ—<‘<Ð DÓE×KÑKˆØ—‘Ó!×! AÒ%Ø$×/Ñ/Ó1×1Ò8°b×?Ñ?ÓAÒIÀTˆEÙÜ)¨)Ó4ˆKÙØ%�ô ' t¨[Ó9×
9€CØ
‡w�wˆxÓ˜DÒ Øˆ
á
�cÓ
€CØ$€CˆÑØŠ˜$€Cˆ�HÙØ$ˆˆKÒÙ	Ü# EÓ*ÒRÔ4LÈTÓ4R×.RˆÜ1°'À3ÐRWÔXˆˆKÑÙØˆˆG‰Ø€Jðm /Sùð ,ùØ)ùð EùØ'ùØ)ùØCùàIùð
 Bùð. DúÜò 	<ÜÐ?À¸yÈÈ3È%ÐPÔQØ%´#°c³(Ñ;Õ;ûð	<úð* 	(ùØ%ùØ2ùð 2ùð 	AùØ#ùØ%ùØ?úÜò NÜÐFÀsÀeÐL×MÒMûðNúð 4ùàùØ6ùà!ùØ1úô
 ò [ÜÐDÀ[ÀMÐQSÐTWÐSXÐY×ZÒZûð[úð :ùð /SúsU  „B	U'ÂRÂU'Â#,R9 ÃRÃR9 Ã"R!Ã#2R9 ÄR$ÄR9 Ä)R'Ä*R9 Ä<R*Ä=R9 ÅR-ÅR9 Å%R0Å&!R9 ÆR3ÆAR9 Ç*R9 ÈU'È R9 È&R6È'R9 È+6U'É"T É3S.É4T ÊS1ÊT ÊS4ÊT Ê4S7Ê5A T Ë5S:Ë6T Ì	S=Ì
T ÌT ÌT Ì1TÌ2T Ì6U'Ì;T: Í
T+Í2T: Í=T.Í>T: ÎT1Î6T: ÏT4ÏT: Ï+T7Ï,+T: ÐU'Ð&U"Ð'AU'Ñ<U%Ñ=U'ÒR9 Ò!R9 Ò$R9 Ò'R9 Ò*R9 Ò-R9 Ò0R9 Ò3R9 Ò6R9 Ò9	S+ÓS&Ó S+Ó!U'Ó&S+Ó+U'Ó.T Ó1T Ó4T Ó7T Ó:T Ó=T Ô T ÔT Ô	T(ÔT#ÔU'Ô#T(Ô(U'Ô+T: Ô.T: Ô1T: Ô4T: Ô7T: Ô:	UÕUÕU'ÕUÕU'Õ%U'c              ƒ   óÀ  K  — t        | |«      ƒ d{  –—† }|j                  «       ƒ d{  –—†  t        dd«      ƒ d{  –—†  |j                  d«      j                  }|j                  «       ƒ d{  –—† dk(  r|j                  d«      }|j                  dd¬	«      ƒ d{  –—†  |j                  «       ƒ d{  –—†  t        d
d«      ƒ d{  –—†  | j                  d«      j                  d«      j                  }|j                  dd¬	«      ƒ d{  –—†  |j                  «       ƒ d{  –—†  t        dd«      ƒ d{  –—†  dddœS 7 �Œ57 �Œ 7 �Œ7 ŒÞ7 Œ±7 Œ›7 Œ‰7 ŒF7 Œ07 Œ­w)z‹Deletes a library video item. In Hudl, clicking delete immediately deletes the video and shows a restore option, without any confirm modal.Nrt   r  rß  r   r[  rB   r\  rD   rr   r@   z$xpath=//button[@aria-label="Delete"]r]  r\   r]   rä   z'Library video item deleted successfullyr:  )rY  rh  r   r,   r-   r.   rR   r  )r#   rG  rV  ri  Údelete_menu_btns        r   Údelete_library_videorb    sG  è ø€ ä˜T ;Ó/×/€DØ
�*‰*‹,×ÐÜ
�c˜3Ó
×Ðð �|‰|ÐTÓU×[Ñ[€HØ�^‰^Ó× Ò"Ø—<‘<Ð 4Ó5ˆà
×
Ñ
 )°TÐ
Ó
:×:Ð:Ø
�.‰.Ó
×ÐÜ
�c˜3Ó
×Ðà—l‘lÐ#IÓJ×RÑRÐSaÓb×hÑh€OØ
×
"Ñ
"¨¸DÐ
"Ó
A×AÐAØ
×
Ñ
Ó
!×!Ð!ô �c˜3Ó
×Ðð Ø<ñð ð) 0ùØùØùð øð ;øØøØøð BøØ!øð  ús®   ‚E’E“EªE
«E¾E¿2EÁ1EÁ2.EÂ EÂ!EÂ8EÂ9EÃEÃAEÄEÄEÄ)EÄ*EÄ=EÄ>
EÅ
EÅEÅEÅEÅEÅEÅEÅEÅEÚexplicit_team_idc              ƒ   óT  K  — |xs dj                  «       }|j                  «       r|S 	 ddlm} ddlm}  |«       5 } ||«      }|r |j                  xs i j                  dd«      nd}t        |«      j                  «       }|j                  «       r|cd d d «       S |r2t        j                  d|«      }|r|j                  d«      cd d d «       S d d d «       | j                  d«      j                  }		 |	j                  d	«      ƒ d {  –—† }
|
r)t        j                  d
|
«      }|r|j                  d«      S t        j                  d| j                   xs d«      }|r|j                  d«      S t#        d«      ‚# 1 sw Y   Œ«xY w# t        $ r Y Œ¹w xY w7 ŒŒ# t        $ r Y Œjw xY w­w)Nr&   r   )ÚSessionLocal)Úget_active_accountr$   z,/(?:watch/team|manage/library|library)/(\d+)r   úS[data-qa-id="webnav-teamswitcher-menu"] > a.hui-primaryteamswitcher__item--selectedr'   r(   zC/(?:watch/team|manage/library|manage/exchanges|library|teams)/(\d+)zmNo Hudl team id found. Select a team in the frontend so Playwright can click it and return a dynamic team_id.)r*   r+   Úapp.core.databasere  Úapp.services.account_servicerf  Úmetadata_jsonr´   rd   r   r   r   r0   r,   r-   r/   r   rT   )r#   rc  rb   re  rf  ÚdbÚaccountÚmeta_idr!   Úselected_teamÚqa_idÚ	url_matchs               r   Ú_resolve_manage_library_team_idrq  :  sŠ  è ø€ ØÒ!˜r×
(Ñ
(Ó
*€CØ
‡{�{„}Øˆ
ðÝ2ÝCáŒ^˜rÙ(¨Ó,ˆGÙJQ�w×,Ñ,Ò2°×7Ñ7¸	À2ÔFÐWYˆGÜ˜'“l×(Ñ(Ó*ˆGØ�‰Ô Ø÷ ‰^ñ ÜŸ	™	Ð"QÐSZÓ[�ÙØ Ÿ;™; q›>÷ �^‰^ð —L‘LØ]óç�eð ðØ#×1Ñ1°,Ó?×?ˆÙÜ—I‘IÐ?ÀÓGˆEÙØ—{‘{ 1“~Ð%ô
 —	‘	ØNØ�‰Š�Bó€Iñ Ø�‰˜qÓ!Ð!ä
ð	5óð ÷C ˆ^ûô ò Ùðúð @ùô
 ò Ùðüs•   ‚'F(ªF ½AE<Â	F ÂF(Â*E<Ã	F ÃF(ÃF ÃF(Ã6F Ä
FÄ.F Ä9AF(Å<FÆF Æ	FÆF(ÆFÆF(ÆF Æ	F%Æ"F(Æ$F%Æ%F(c              ƒ   óÊ  K  — ddl m} d}d| j                  vr/| j                  |d¬«      ƒ d{  –—†  t	        dd	«      ƒ d{  –—†  d
| j                  vr/| j                  |d¬«      ƒ d{  –—†  t	        dd	«      ƒ d{  –—†  t        | «      ƒ d{  –—†  | j                  d«      j                  }	 |j                  d|¬«      ƒ d{  –—†  |j                  d«      ƒ d{  –—† }|r|j                  |«      ry| j                  d|› d�«      j                  }	 |j                  «       ƒ d{  –—† dkD  rB|j                  |¬«      ƒ d{  –—†  t	        dd«      ƒ d{  –—†  t        | «      ƒ d{  –—†  y	 | j                  d«      j                  }	 |j                  «       ƒ d{  –—† dk(  rt        d|› d�«       y|j                  |¬«      ƒ d{  –—†  t	        dd«      ƒ d{  –—†  | j                  d|› d�«      j                  }|j                  d|¬«      ƒ d{  –—†  |j                  |¬«      ƒ d{  –—†  t	        dd«      ƒ d{  –—†  t        | «      ƒ d{  –—†  y7 �Œ<7 �Œ+7 �Œ7 �Œô7 �Œä7 �Œ°7 �Œš# t        $ r Y �Œ�w xY w7 �Œa7 �ŒG7 �Œ67 �Œ&# t        $ r Y �Œ.w xY w7 �Œ7 ŒØ7 ŒÆ7 ŒŽ7 Œv7 Œd7 ŒS# t        $ r}t        d|› d|› d�«       Y d}~yd}~ww xY w­w)ué   Best-effort switch of the active Hudl team on the modern home navbar.

    Returns True when the requested team appears selected. Manage Library does
    not require this â€” the team id is embedded in /manage/library/{team_id}.
    r   ©ÚHUDL_HOME_URLr\  úhudl.comr=   r>   Nr@   rA   r;   rg  rG   rD   r'   Tz&[data-qa-id="webnav-teamswitcher-team-r5  rq   r\   rÝ   ú'[data-qa-id="webnav-teamswitcher-menu"]u9   Team switcher not on current page â€” skipping switch to z$ (will use direct team URL instead).Frr   r"  rB   zCould not switch Hudl team to rÞ   z,. Continuing with direct Manage Library URL.)rç   rt  r   rQ   r   r   r,   r-   rR   r/   Úendswithr0   r.   r  r^   )	r#   r$   rt  Úshort_timeoutr5   ro  Ú	team_linkÚswitcher_btnrm   s	            r   Ú_switch_hudl_team_by_idr{  j  sá  è ø€ õ 9à€Mà˜Ÿ™Ñ!Ø�i‰i˜Ð2DˆiÓE×EÐEÜ˜#˜sÓ#×#Ð#à˜dŸh™hÑ&Ø�i‰i˜Ð2DˆiÓE×EÐEÜ˜#˜sÓ#×#Ð#Ü
˜DÓ
!×!Ð!à�|‰|Ø]óç�eð ðØ×Ñ j¸-ÐÓH×HÐHØ×,Ñ,¨\Ó:×:ˆÙ�U—^‘^ GÔ,Øð —‘ÐEÀgÀYÈbÐQÓR×XÑX€IðØ—‘Ó"×" QÒ&Ø—/‘/¨-�/Ó8×8Ð8Ü˜c 3Ó'×'Ð'Ü# DÓ)×)Ð)Øð	 'ð —<‘<Ð IÓJ×PÑP€LðØ×#Ñ#Ó%×%¨Ò*ÜØKÈGÈ9ð U6ð 6ôð à× Ñ ¨Ð Ó7×7Ð7Ü˜#˜sÓ#×#Ð#Ø—L‘LÐ#IÈ'ÈÐRTÐ!UÓV×\Ñ\ˆ	Ø× Ñ  y¸-Ð ÓH×HÐHØ�o‰o mˆoÓ4×4Ð4Ü˜#˜sÓ#×#Ð#Ü Ó%×%Ð%Øð[ 	FùØ#ùð 	FùØ#ùØ!ùð 	IùØ:úô ò Úðúð
 #ùØ8ùØ'ùØ)úäò Úðúð
 &ùð 	8øØ#øàHøØ4øØ#øØ%ùäò ÜØ,¨W¨I°R¸°uð =9ð 9ô	
ô ûðüs¿  ‚-K#¯I+°K#ÁI.Á(K#Á,I1Á-K#Â I4ÂK#ÂI7ÂK#Â4J  Ã
I:ÃJ  Ã#I=Ã$J  Ã; K#ÄJ Ä/JÄ0J ÅJÅJ Å JÅ!J Å3JÅ4J Å8K#ÆJ; Æ)J,Æ*J; Ç K#ÇJ; ÇJ/ÇJ; Ç*J1Ç+9J; È$J3È%J; È>J5È?J; ÉJ7ÉJ; É%J9É&J; É*K#É.K#É1K#É4K#É7K#É:J  É=J  Ê 	JÊ	K#ÊJÊK#ÊJ ÊJ ÊJ ÊJ Ê	J)Ê%K#Ê(J)Ê)K#Ê,J; Ê/J; Ê1J; Ê3J; Ê5J; Ê7J; Ê9J; Ê;	K ËKËK#ËK Ë K#c                 ó`   — t        j                  d| xs d«      }|r|j                  d«      S d S )Nz=/(?:watch/team|manage/library|manage/exchanges|library)/(\d+)r&   r   r   r    s     r   Ú_extract_team_id_from_urlr}  «  s1   € Ü�I‰IØHØŠ	ˆró€Eñ #ˆ5�;‰;�q‹>Ð,¨Ð,r   c                 ót   — | xs dj                  «       }d|v ryd|v st        j                  d|«      rd|vryy)uE   Map Hudl landing URL â†’ FE library mode (no school-name hardcoding).r&   ú/manage/library/rÐ   zwww.hudl.com/library/zhudl\.com/library/\d+zapp.hudl.comÚ	dashboard)r   r   r   )r   rô   s     r   Ú_library_action_from_urlr�  ³  sC   € à	Š�×ÑÓ€AØ˜QÑØØ !Ñ#¤r§y¡yÐ1IÈ1Ô'MØ Ñ"ØØr   c              ƒ   óÐ  K  — ddl m} t        | d¬«      ƒ d{  –—†  t        | «      ƒ d{  –—†  dd|df}d}d	}|D �]y  }	 |j	                  d
«      | j
                  xs dvrC| j                  |dd¬«      ƒ d{  –—†  t        dd«      ƒ d{  –—†  t        | «      ƒ d{  –—†  t        d«      D ]ÿ  }	 t        | «      ƒ d{  –—†  d}|D ]<  }	| j                  |	«      j                  }
|
j                  «       ƒ d{  –—† dkD  sŒ:|
} n |st        d«      ‚|j                  dd¬«      ƒ d{  –—†  	 |j!                  d¬«      ƒ d{  –—†  t        dd«      ƒ d{  –—†  | j                  d«      j                  j                  d d¬«      ƒ d{  –—†  d}t        d!| j
                  › �«        n |s�Œz n |st        d)«       | j'                  d*«      ƒ d{  –—† }	 | j"                  j%                  d&«      ƒ d{  –—†  t        d+d«      ƒ d{  –—†  |xs i j)                  d,«      xs g }t+        |xs i j)                  d-«      xs d«      }t+        |xs i j)                  d.«      xs d«      }t        d/t-        |«      › d0|› d1|› d2|› d3| j
                  › d4�«       d5d6|t-        |«      || j
                  |rd7|› d8t-        |«      › d9�d;œS d:d;œS 7 �Œ½7 �Œ­7 �Œb7 �ŒQ7 �ŒA# t        $ r}t        d|› d|› �«       Y d}~�ŒÈd}~ww xY w7 �ŒJ7 �Œ7 �Œå7 �ŒÍ# t        $ r  |j!                  dd¬«      ƒ d{  –—†7   Y �Œów xY w7 �Œè7 �Œ·# t        $ rv}t        d"|d#z   › d$| j
                  › d%|› �«       	 | j"                  j%                  d&«      ƒ d{  –—†7   n# t        $ r Y nw xY wt        d'd(«      ƒ d{  –—†7   Y d}~�Œd}~ww xY w7 �Œò7 �ŒÑ7 �ŒÀ# t        $ r Y �ŒÊw xY w­w)<uå   
    Open Hudl team switcher and return every school â†’ team the account can access.
    Tries app.hudl.com first (modern nav) then classic home â€” production often
    lands on /library/{id} where the switcher is missing.
    r   rs  rP   ©Ú	probe_urlNzhttps://app.hudl.com/homezhttps://www.hudl.com/)rv  z(.hui-primaryteamswitcher__item--selectedz"[data-qa-id="webnav-teamswitcher"]z&button.hui-primaryteamswitcher__buttonz.hui-primaryteamswitcherz[class*="primaryteamswitcher"]FrK   r&   r=   rK  ©r?   rF   rr   r"  z,list_hudl_organizations: navigation failed (z): rN  zteam switcher control not foundrB   r^  rD   ép  rq   rî  T©rF   rà  rt   ru   ú)[data-qa-id^="webnav-teamswitcher-team-"]rG   z,list_hudl_organizations: switcher opened on z/list_hudl_organizations: open switcher attempt r   z on rÞ   rƒ  rÝ  rì  uS   list_hudl_organizations: team switcher did not open â€” scraping whatever is in DOMu³  () => {
          const slug = (s) => (s || '')
            .toLowerCase()
            .replace(/[^a-z0-9]+/g, '-')
            .replace(/^-+|-+$/g, '') || 'org';

          const inferIcon = (name) => {
            const n = (name || '').toLowerCase();
            if (n.includes('football')) return 'football';
            if (n.includes('basketball')) return 'basketball';
            if (n.includes('soccer')) return 'soccer';
            if (n.includes('lacrosse')) return 'lacrosse';
            if (n.includes('volleyball')) return 'volleyball';
            if (n.includes('baseball') || n.includes('softball')) return 'baseball';
            return 'users';
          };

          const orgs = [];
          const orgByName = new Map();
          const ensureOrg = (orgName) => {
            const name = (orgName || 'Organizations').trim() || 'Organizations';
            if (orgByName.has(name)) return orgByName.get(name);
            const org = { id: slug(name), name, type: 'dashboard', teams: [] };
            orgByName.set(name, org);
            orgs.push(org);
            return org;
          };

          const inferAction = (href) => {
            const h = String(href || '');
            // Classic Manage Library routes â†’ FE /admin/manage-library (URL only).
            if (/\/manage\/library\//i.test(h)) return 'library';
            if (/www\.hudl\.com\/library\//i.test(h)) return 'library';
            return 'dashboard';
          };

          const pushTeam = (orgName, teamName, teamId, photoUrl, href) => {
            const name = (teamName || '').trim().split('\n')[0].trim();
            const id = String(teamId || '').trim();
            if (!name || !/^\d+$/.test(id)) return;
            const org = ensureOrg(orgName);
            if (org.teams.some((t) => t.hudl_team_id === id)) return;
            const icon = inferIcon(name);
            const action = inferAction(href);
            org.teams.push({
              id: `hudl-${id}`,
              name,
              hudl_team_id: id,
              icon,
              photo_url: photoUrl || null,
              action,
            });
            if (action === 'library') org.type = 'library';
          };

          const isOrgHeaderText = (text) => {
            const t = (text || '').trim().split('\n')[0].trim();
            if (!t || t.length > 100) return false;
            return /high school|catholic|academy|college|university|school$/i.test(t);
          };

          const menuRoot =
            document.querySelector('[data-qa-id="webnav-teamswitcher-menu"]')?.parentElement ||
            document.querySelector('.hui-primaryteamswitcher') ||
            document.querySelector('[class*="TeamSwitcher"]') ||
            document.querySelector('[class*="team-switcher"]') ||
            document.body;

          let currentOrg = 'Organizations';
          const walk = (root) => {
            const nodes = Array.from(root.querySelectorAll('*'));
            for (const el of nodes) {
              const qa = el.getAttribute('data-qa-id') || '';
              const teamMatch = qa.match(/webnav-teamswitcher-team-(\d+)/);
              if (teamMatch) {
                const teamId = teamMatch[1];
                const teamName = (el.innerText || '').trim().split('\n')[0].trim();
                const img = el.querySelector('img');
                const anchor = el.closest('a') || (el.tagName === 'A' ? el : null);
                const href = anchor?.getAttribute?.('href') || '';
                pushTeam(currentOrg, teamName, teamId, img?.src || null, href);
                continue;
              }

              if (el.children && el.children.length === 0) {
                const t = (el.innerText || '').trim();
                if (isOrgHeaderText(t) && !qa.includes('team-')) {
                  currentOrg = t.split('\n')[0].trim();
                }
              }
            }
          };
          walk(menuRoot);

          if (!orgs.some((o) => o.teams.length)) {
            document.querySelectorAll('[data-qa-id^="webnav-teamswitcher-team-"]').forEach((a) => {
              const qa = a.getAttribute('data-qa-id') || '';
              const m = qa.match(/webnav-teamswitcher-team-(\d+)/);
              const teamId = m ? m[1] : '';
              const teamName = (a.innerText || '').trim().split('\n')[0].trim();
              let orgName = 'Organizations';
              let prev = a.previousElementSibling;
              for (let i = 0; i < 8 && prev; i++) {
                const t = (prev.innerText || '').trim().split('\n')[0];
                if (isOrgHeaderText(t)) { orgName = t; break; }
                prev = prev.previousElementSibling;
              }
              const group = a.closest('[class*="group"], [class*="section"], [role="group"]');
              const header = group?.querySelector('[class*="header"], [class*="title"], h3, h4, strong');
              if (header && isOrgHeaderText(header.innerText || '')) {
                orgName = (header.innerText || '').trim().split('\n')[0];
              }
              const img = a.querySelector('img');
              pushTeam(orgName, teamName, teamId, img?.src || null, a.getAttribute('href') || '');
            });
          }

          // Classic library left nav: school â†’ team links with /library/{id}.
          if (!orgs.some((o) => o.teams.length)) {
            const left =
              document.querySelector('#leftColumnDiv, .library-left, [class*="team-list"]') ||
              document.body;
            let orgName = 'Organizations';
            left.querySelectorAll('a[href*="/library/"], a[href*="/manage/library/"]').forEach((a) => {
              const href = a.getAttribute('href') || '';
              const m = href.match(/\/(?:manage\/)?library\/(\d+)/);
              if (!m) return;
              const teamName = (a.innerText || '').trim().split('\n')[0].trim();
              if (!teamName || teamName.length > 80) return;
              if (isOrgHeaderText(teamName)) {
                orgName = teamName;
                return;
              }
              pushTeam(orgName, teamName, m[1], a.querySelector('img')?.src || null, href);
            });
          }

          orgs.sort((a, b) => a.name.localeCompare(b.name));

          return {
            organizations: orgs.filter((o) => o.teams.length > 0),
            teams_count: orgs.reduce((n, o) => n + o.teams.length, 0),
            debug_link_count: document.querySelectorAll('[data-qa-id^="webnav-teamswitcher-team-"]').length,
          };
        }rv   ÚorganizationsÚteams_countÚdebug_link_countzlist_hudl_organizations: z orgs, z teams, links=z opened=z (url=r.  rä   Úlist_hudl_organizationsrå   z teams across z	 schools.zHNo Handshake teams found in the team switcher. Open Organizations again.)r�   Úactionr‰  Úorganizations_countrŠ  r   rƒ   )rç   rt  Ú_ensure_hudl_sessionr   r)  r   rQ   r   r0   r^   r…  r,   r-   r.   ÚRuntimeErrorrR   r  r†  r‡  rj   r´   rµ   r¹   )r#   rt  rñ   Úswitcher_selectorsÚopenedr   rm   ÚattemptÚswitcherr?  r  rb   r‰  rŠ  Údebug_linkss                  r   rŒ  rŒ  ¾  s  è ø€ õ 9ä
˜tÐ/FÔ
G×GÐGÜ
˜DÓ
!×!Ð!ð 	 Ø#ØØð	€NðÐð €FÜˆð	Ø�z‰z˜#‹ t§x¡x¢~°2Ñ6Ø—i‘i Ð0BÈE�iÓR×RÐRÜ! # sÓ+×+Ð+Ü'¨Ó-×-Ð-ô
 ˜Q–xˆGð,Ü'¨Ó-×-Ð-Ø�Û-�CØŸ,™, sÓ+×1Ñ1�CØ ŸY™Y›[×(¨1Ó,Ø#&˜Ùð	 .ñ
  Ü&Ð'HÓIÐIØ×'Ñ'¨iÀÐ'ÓF×FÐFðCØ"Ÿ.™.°˜.Ó6×6Ð6ô " # sÓ+×+Ð+Ø—l‘lØ?óç‘%Ÿ™ z¸4˜Ó@÷Að Að �ÜÐDÀTÇXÁXÀJÐOÔPÙð-  óB ÙðY ñ\ ÜØaô	
ð —‘ðP	óR÷ R€CðhØ�m‰m×!Ñ! (Ó+×+Ð+Ü˜#˜sÓ#×#Ð#ð ’Y˜B—O‘O OÓ4Ò:¸€MÜ�s’y˜b—o‘o mÓ4Ò9¸Ó:€KÜ�s’y˜b—o‘oÐ&8Ó9Ò>¸QÓ?€KÜ	Ø
#¤C¨Ó$6Ð#7°w¸{¸mð LØ�˜X f X¨V°D·H±H°:¸Qð	@ôð
 Ø+Ø&Ü" =Ó1Ø"Ø�x‰xñ ð �[�M ´°MÓ0BÐ/CÀ9ÐMñð ð \ñð ðQ HùØ!ùð, SùØ+ùØ-úÜò 	ÜÐ@ÀÀÀSÈÈÐNÔOÝûð	úð .ùð )ùð
 Gùà6úÜ ò CØ"Ÿ.™.°¸T˜.ÓB×BÔBðCúà+ùðAúô ò 	,ÜØEØ ‘{�m 4¨¯© z°°C°5ð:ôðØŸ-™-×-Ñ-¨hÓ7×7Ò7øÜ ò Ùðúä! # sÓ+×+×+ûð	,úð$Rùðj 	,ùØ#úÜò ÚðüsÜ  ‚O&™KšO&¬K­O&Á8K%Á:KÁ;K%ÂKÂK%Â!K"Â"K%Â&O&Â5MÃLÃ9MÃ=L
Ã>MÄ(MÄ.LÄ/MÄ4LÅ	LÅ
LÅMÅMÅ3MÆMÆMÆ0O&Æ7$O&ÇOÇO&Ç!O Ç?OÈ O ÈOÈO ÈB?O&ËO&ËK%ËK%Ë"K%Ë%	L
Ë.LË?O&ÌL
Ì
O&ÌMÌMÌMÌLÌMÌ8L;
Ì9MÌ>MÍMÍMÍMÍ	O
Í!OÍ6NÎN
ÎNÎOÎ	N'	Î$OÎ&N'	Î'OÎ9N<
Î:OÎ?O&ÏO
Ï
O&ÏO ÏO Ï	O#ÏO&Ï"O#Ï#O&r&   Ú	team_nameÚorg_namec              ƒ   óÐ  ‡‡K  — ddl m} |xs dj                  «       }|xs dj                  «       Š|s‰j                  «       st	        d«      ‚‰j                  «       rî| j
                  xs dŠt        ˆˆfd„dD «       «      }|sId‰› �}	 | j                  |dd	¬
«      ƒ d{  –—†  t        dd«      ƒ d{  –—†  t        | «      ƒ d{  –—†  t        | j
                  xs d«      xs ‰}t        d|›d|›d|› d| j
                  › �«       dd|xs d|› �||| j
                  t        | j
                  xs d«      ddd|xs |› d|› d�dœ
S t        | |¬«      ƒ d{  –—†  t        | «      ƒ d{  –—†  d| j
                  xs dvrC| j                  |dd	¬
«      ƒ d{  –—†  t        d d!«      ƒ d{  –—†  t        | «      ƒ d{  –—†  d}d}|�r	 | j                  d"«      j                   }|j#                  «       ƒ d{  –—† dkD  �rÌ|j%                  d#¬$«      ƒ d{  –—†  t        d%d&«      ƒ d{  –—†  | j                  d'«      j'                  t)        j*                  d(t)        j,                  |«      › d)�t(        j.                  «      ¬*«      j                   }|j#                  «       ƒ d{  –—† dk(  rV| j1                  d+t)        j*                  d(t)        j,                  |«      › d)�t(        j.                  «      ¬,«      j                   }|j#                  «       ƒ d{  –—† dk(  r| j                  d-|› d.�«      j                   }|j#                  «       ƒ d{  –—† dkD  rt|j3                  d/«      ƒ d{  –—† xs d}|j%                  d0¬$«      ƒ d{  –—†  t        d d!«      ƒ d{  –—†  t        | «      ƒ d{  –—†  d1}d2}t        d3|›d4|› d�«       |�s[|�rX	 d6| j
                  xs dvrUd7| j
                  xs dvrC| j                  d8dd	¬
«      ƒ d{  –—†  t        d d!«      ƒ d{  –—†  t        | «      ƒ d{  –—†  | j                  d9|› d:�«      j                   | j5                  |d1¬;«      j                   | j                  d<|› d=|› d>|› d.�«      j                   g}|D ]‹  }	 |j#                  «       ƒ d{  –—† dk(  rŒ |j7                  «       ƒ d{  –—†  |j%                  d#¬$«      ƒ d{  –—†  t        d d!«      ƒ d{  –—†  t        | «      ƒ d{  –—†  d1}d?}t        d@|›�«        n t        | j
                  xs d«      }|s}	 | j                  dB«      j                   }|j#                  «       ƒ d{  –—† dkD  rF|j3                  d/«      ƒ d{  –—† xs d}t)        j8                  dC|«      }|r|j;                  dD«      }|r�d|› �}d6|› �| j
                  xs dvrvdE|› �| j
                  xs dvra	 | j                  |dd	¬
«      ƒ d{  –—†  t        dFdG«      ƒ d{  –—†  t        | «      ƒ d{  –—†  t        | j
                  xs d«      xs |}|s t	        dI|xs ‰›dJ| j
                  › dK�«      ‚t        dL|›d|›d|› dM|› dN| j
                  › �
«       dd|xs d|› �||| j
                  t        | j
                  xs d«      ||d|xs |› d|› d�dœ
S 7 �Œ@7 �Œ/7 �Œ# t        $ rŠ}t        d|› �«       	 d‰› �}	| j                  |	dd	¬
«      ƒ d{  –—†7   t        dd«      ƒ d{  –—†7   t        | «      ƒ d{  –—†7   n$# t        $ r}
t        d|
› �«       Y d}
~
nd}
~
ww xY wY d}~�Œ«d}~ww xY w7 �Œ%7 �Œ7 �Œê7 �ŒÙ7 �ŒÉ7 �Œ‘7 �Œv7 �Œe7 �Œì7 �Œ~7 �ŒG7 �Œ.7 �Œ7 �Œ7 �Œò# t        $ r}t        d5|› �«       Y d}~�Œ÷d}~ww xY w7 �Œ½7 �Œ¬7 �Œœ7 �Œ!7 �Œ7 �Œñ7 �Œà7 �ŒÐ# t        $ r Y �ŒSw xY w# t        $ r}t        dA|› �«       Y d}~�Œæd}~ww xY w7 �Œ¤7 �Œ‹# t        $ r Y �Œhw xY w7 �Œ#7 �Œ7 �Œ# t        $ r}t        dH|› �«       Y d}~�Œ d}~ww xY w­w)Oz÷Switch Hudl to a team and return its team id.

    Fast path: when team_id is known, go straight to /library/{id} (no navbar
    switcher). The switcher is often missing on blank lane tabs and was the
    main reason team switches felt stuck.
    r   rs  r&   z team_name or team_id is requiredc              3   ó2   •K  — | ]  }d |› d ‰› �‰v –— Œ y­w)rK   NrÊ   )rË   Úsegr*  Úexplicit_ids     €€r   rÎ   z+switch_hudl_team_by_name.<locals>.<genexpr>Ô	  s/   øè ø€ ð 	
ñ�ð �ˆu�A�k�]Ð# wÔ.ñùs   ƒ)rÐ   rY   zmanage/exchangesrM   Úteamsr   r=   rK  r…  Nr  rw   z&Fast team switch library goto failed: ú$https://www.hudl.com/manage/library/z-Fast team switch manage-library goto failed: z Hudl team resolved (fast): name=z org=z	 team_id=z method=direct_url url=rä   Úswitch_hudl_teamzteam-FÚ
direct_urlzSwitched Hudl to z
 (team_id=r.  )
r�   r�  r–  r—  r$   r   Úlibrary_actionr‘  Úmethodrƒ   rƒ  ru  r  r@   rv  r†  rq   rÝ  rì  rˆ  Ú^Ú$©Úhas_textr<  )r‡   z4[data-qa-id^="webnav-teamswitcher-team-"]:has-text("rD  r'   r^  TÚteam_switcherzHudl team switcher clicked: r÷  z$Team switcher click by name failed: rL   z/manage/r<   ztext="rC  ©Úexactza:has-text("z"), button:has-text("z"), li:has-text("Úsidebar_textzHudl sidebar team clicked: z#Sidebar team click by name failed: z¦[data-qa-id="webnav-teamswitcher-menu"] > a.hui-primaryteamswitcher__item--selected, [data-qa-id^="webnav-teamswitcher-team-"].hui-primaryteamswitcher__item--selectedr(   r   r  rt   rr   z'Post-switch library navigation failed: zClicked team z+ but could not read team id from Hudl URL (z).zHudl team resolved: name=z method=z url=)rç   rt  r*   r+   rT   r   rÖ   rQ   r   r   r0   r^   r}  r�  r�  r,   r-   r.   r  Úfilterr   re   Úescaperf   Úget_by_roler/   Úget_by_textrR  r   r   )r#   r–  r—  r$   rt  r‡   Úalready_on_teamr`   rm   Ú
manage_urlÚexc2Úresolved_idr‘  r¡  r”  r¤  r6   Ú
candidatesr  r5   r7   r*  r›  s                        @@r   Úswitch_hudl_team_by_namer³  ½	  s§  ùè ø€ õ 9àŠO˜×"Ñ"Ó$€DØ’=˜b×'Ñ'Ó)€Ká˜×+Ñ+Ô-ÜÐ;Ó<Ð<ð ×ÑÔØ—(‘(’.˜bˆÜô 	
ñó	
ó 	
ˆñ Ø9¸+¸ÐGˆKðRØ—i‘iØÐ,>Èð  ó ÷ ð ô " $¨Ó,×,Ð,Ü'¨Ó-×-Ð-ô 0°·±²¸BÓ?ÒNÀ;ˆÜØ.¨t¨h°e¸H¸<ð HØ"�mÐ#:¸4¿8¹8¸*ðFô	
ð
 Ø(ØÒ6 5¨¨Ð!6Ø Ø"Ø—8‘8Ü6°t·x±x²~À2ÓFØØ"Ø*¨4Ò+>°;Ð*?¸zÈ+ÈÐVWÐXñ
ð 	
ô ˜t¨}Ô
=×=Ð=Ü
˜DÓ
!×!Ð!ð ˜$Ÿ(™(š. bÑ)Ø�i‰i˜Ð2DÈeˆiÓT×TÐTÜ˜#˜sÓ#×#Ð#Ü Ó%×%Ð%à€GØ€Fò ð	@Ø—|‘|Ð$MÓN×TÑTˆHØ—^‘^Ó%×%¨Ó)Ø—n‘n¨T�nÓ2×2Ð2Ü! # sÓ+×+Ð+ØŸ™Ø?óç‘&¤"§*¡*°´"·)±)¸D³/Ð1BÀ!Ð-DÄbÇdÁdÓ"K�&ÓLÏUÉUð ð  Ÿ™›×'¨1Ò,Ø!×-Ñ-¨f¼2¿:¹:ÈÌ2Ï9É9ÐUYË?ÐJ[Ð[\ÐF]Ô_a×_cÑ_cÓ;dÐ-Óe×kÑk�FØŸ™›×'¨1Ò,Ø!Ÿ\™\ØNÈtÈfÐTVÐWóç‘eð ð  Ÿ™›×'¨!Ò+Ø &× 4Ñ 4°\Ó B×BÒIÀr�BØ Ÿ,™,¨t˜,Ó4×4Ð4Ü% c¨3Ó/×/Ð/Ü+¨DÓ1×1Ð1Ø"�GØ,�FÜÐ8¸¸ÀÀ2À$ÀaÐHÔIò
 ’tð 	?Ø 4§8¡8¢>¨rÑ2°zÈ$Ï(É(Ê.ÐVXÑ7YØ—i‘iØ/Ø1Ø!ð  ó ÷ ð ô
 " # sÓ+×+Ð+Ü'¨Ó-×-Ð-ð —‘˜v d V¨1Ð-Ó.×4Ñ4Ø× Ñ  ¨TÐ Ó2×8Ñ8Ø—‘Ø" 4 &Ð(=¸d¸VÐCTÐUYÐTZÐZ\Ð]óç‘%ðˆJó "�ðØ ŸY™Y›[×(¨AÒ-Ø Ø×8Ñ8Ó:×:Ð:ØŸ)™)¨D˜)Ó1×1Ð1Ü% c¨3Ó/×/Ð/Ü+¨DÓ1×1Ð1Ø"�GØ+�FÜÐ7¸°xÐ@ÔAÙð "ô" ,¨D¯H©HªN¸Ó;€Káð	Ø—|‘|ðdó÷ ‰eð ð —^‘^Ó%×%¨Ò)Ø$×2Ñ2°<Ó@×@ÒGÀR�Ü—I‘IÐ?ÀÓD�ÙØ"#§'¡'¨!£*�Kñ Ø5°k°]ÐCˆà˜�}Ð%¨d¯h©hªn¸"Ñ=Ø" ; -Ð0¸¿¹ºÀRÑHðGØ—i‘iØÐ,>Èð  ó ÷ ð ô " # sÓ+×+Ð+Ü'¨Ó-×-Ð-Ü7¸¿¹ºÀBÓGÒVÈ;�ñ ÜØ˜DÒ/ KÐ2ð 3Ø—‘ˆz˜ðó
ð 	
ô
 
Ø
# D 8¨5°°ð =Ø�-˜x¨ x¨u°T·X±X°Jð	@ôð
 Ø$ØÒ2˜u [ MÐ2ØØØ�x‰xÜ2°4·8±8²>¸rÓBØØØ& tÒ':¨{Ð&;¸:ÀkÀ]ÐRSÐTñð ðqùð -ùØ-úÜò 
RÜÐ>¸s¸eÐDÔEðRØ#GÈÀ}Ð!U�JØŸ)™)Ø"Ð/AÈ5ð $ó ÷ ñ ô & d¨CÓ0×0Ñ0Ü+¨DÓ1×1Ò1øÜ ò RÜÐIÈ$ÈÐP×QÑQûðRÿüð
Rúð< >ùØ!ùð 	UùØ#ùØ%ùð &ùØ2ùØ+ùð (ùà'ùð (ùØBùØ4ùØ/ùØ1úô ò 	@ÜÐ8¸¸Ð>×?Ò?ûð	@úðùð
 ,ùØ-ùð )ùà:ùØ1ùØ/ùØ1úô
 !ò Úðûäò 	?ÜÐ7¸°uÐ=×>Ò>ûð	?úð &ùØ@úô ò 	Úð	úðùð ,ùØ-úäò GÜÐ?À¸uÐE×FÒFûðGüs}  „Ba&Â[- Â$[$Â%[- Â8['Â9[- Ã[*Ã[- ÃBa&Å^Åa&Å1^Å2-a&Æ^	Æ a&Æ3^Æ4a&Ç^Ça&Ç.^0 È^È^0 È^È ^0 È3^È4A;^0 Ê/^Ê0A0^0 Ì ^Ì!9^0 Í^!Í^0 Í6^$Í7^0 Î^'Î^0 Î(^*Î)^0 Î;^-Î<^0 Ïa&Ï;_= Ð_Ð_= Ð,_Ð-_= Ð?_Ñ A)_= Ò*_-Ò=_Ò>_-Ó_= Ó_-Ó_!Ó_-Ó3_$Ó4_-Ô_'Ô_-Ô_*Ô_-Ô1_= Ô4a&Õ.`( Õ>`"Õ?`( Ö`%Ö1`( ×1a&×>a Ø`8Øa Ø)`;Ø*a Ø<`>Ø=!a ÙBa&Û$[- Û'[- Û*[- Û-	^ Û6];Ü]Ü!\$Ü"]Ü6\9Ü7]Ý
]Ý]Ý];Ý	]2Ý]-Ý(];Ý-]2Ý2];Ý5a&Ý;^ Þ a&Þa&Þ	a&Þa&Þa&Þ^0 Þ^0 Þ^0 Þ^0 Þ^0 Þ!^0 Þ$^0 Þ'^0 Þ*^0 Þ-^0 Þ0	_Þ9_ßa&ß_ßa&ß_= ß_= ß_= ß_-ß!_-ß$_-ß'_-ß*_-ß-	_:ß6_= ß9_:ß:_= ß=	`à`àa&à`àa&à"`( à%`( à(	`5à1a&à4`5à5a&à8a à;a à>a á	a#á
aáa&áa#á#a&c              ƒ   óz  K  — ddl m} d|› �}d|› �| j                  xs dv rKt        d| j                  › d�«       t	        | «      ƒ d{  –—†   || |d	¬
«      ƒ d{  –—†  | j                  S t        d|› �«       | j                  |d¬«      ƒ d{  –—†  t        dd«      ƒ d{  –—†  t	        | «      ƒ d{  –—†   || |d	¬
«      ƒ d{  –—†  t        t        dddfD ]9  }	 | j                  |«      j                  j                  dd¬«      ƒ d{  –—†   |S  |S 7 ŒÓ7 ŒÂ7 Œ�7 Œ}7 Œl7 Œ[7 Œ# t        $ r Y ŒWw xY w­w)zOOpen Hudl Classic Manage Library: https://www.hudl.com/manage/library/{team_id}r   )Úraise_if_hudl_forbiddenr�  r  r&   zAlready on Manage Library (u   ) â€” skipping reloadNrÐ   )r$   Úareaz Navigating to Manage Library at r=   r>   rÝ  rr   ú.manage-library-right-columnúul.jstree-container-ulz[data-qa-id="primary-navbar"]rG   rC   rD   )Ú app.automation.tasks.permissionsrµ  r   r^   r   rQ   r   r	   r
   r,   r-   rR   r0   )r#   r$   rµ  r   r  s        r   Ú_navigate_to_manage_libraryrº  ‡
  sR  è ø€ åHà0°°	Ð
:€Cà	˜'˜Ð#¨¯©ª°BÑ7ÜÐ+¨D¯H©H¨:Ð5JÐKÔLÜ Ó%×%Ð%Ù% d°GÀ)ÔL×LÐLØ�x‰xˆä	Ð,¨S¨EÐ
2Ô3Ø
�)‰)�CÐ$6ˆ)Ó
7×7Ð7ä
�c˜3Ó
×ÐÜ
˜DÓ
!×!Ð!Ù
! $°¸iÔ
H×HÐHô 	ÜØ&Ø Ø'óˆð	Ø—,‘,˜xÓ(×.Ñ.×7Ñ7¸jÐRWÐ7ÓX×XÐXØð €Jðð €Jð1 	&øØLøð 8øàøØ!øØHøð Yùäò 	Ùð	üs¢   ‚AD;Á
DÁD;ÁD Á4D;ÂD"ÂD;Â&D$Â'D;Â9D&Â:D;ÃD(ÃD;Ã$/D,ÄD*ÄD,ÄD;Ä D;Ä"D;Ä$D;Ä&D;Ä(D;Ä*D,Ä,	D8Ä5D;Ä7D8Ä8D;c              ƒ   óf  K  — dddddt         t        f}|D ]8  }	 | j                  |«      j                  j	                  dd¬«      ƒ d	{  –—†   y	 |D ]8  }	 | j                  |«      j                  j	                  dd
¬«      ƒ d	{  –—†   y	 y	7 ŒE# t
        $ r Y Œ†w xY w7 Œ# t
        $ r Y ŒZw xY w­w)zAWait for the Manage Library tree (jstree or classic toggle tree).z##libraryView ul.jstree-container-ulz3.manage-library-right-column ul.jstree-container-ulr¸  z.tree ul.t2z	li.togglerG   iX  rD   NrM  )r	   r
   r,   r-   rR   r0   )r#   Ú	selectorsr  s      r   Ú_wait_for_manage_library_treer½  ª
  sÍ   è ø€ ð 	.Ø=Ø ØØÜÜð€Ió ˆð	Ø—,‘,˜xÓ(×.Ñ.×7Ñ7¸jÐRUÐ7ÓV×VÐVÙð ó ˆð	Ø—,‘,˜xÓ(×.Ñ.×7Ñ7¸jÐRWÐ7ÓX×XÐXÙñ ð	 Wùäò 	Ùð	úð Yùäò 	Ùð	üsp   ‚B1š/BÁ	BÁ
BÁB1Á/B"ÂB ÂB"ÂB1ÂBÂ	BÂB1ÂBÂB1Â B"Â"	B.Â+B1Â-B.Â.B1Ú
max_clicksc              ƒ   ó>  K  — d}||k  r}| j                  d«      }|j                  «       ƒ d{  –—† }|dk(  ry	 |j                  j                  d¬«      ƒ d{  –—†  |dz  }t	        j
                  d«      ƒ d{  –—†  ||k  rŒ|yy7 ŒY7 Œ07 Œ# t        $ r Y yw xY w­w)zSExpand a limited number of collapsed tree nodes (avoids hanging on huge libraries).r   ú.jstree-closed > .jstree-oclNéÐ  rq   r   ç333333Ã?)r,   r.   r-   r  r<  Úsleepr0   )r#   r¾  ÚclicksÚclosed_iconsÚclosed_counts        r   Ú_expand_manage_library_treerÇ  Ä
  s£   è ø€ à€FØ
�:Ò
Ø—|‘|Ð$BÓCˆØ)×/Ñ/Ó1×1ˆØ˜1ÒØð	Ø×$Ñ$×*Ñ*°4Ð*Ó8×8Ð8Ø�a‰KˆFÜ—-‘- Ó%×%Ð%ð �:Õ
à1øð
 9øà%ùÜò 	Ùð	üs\   ‚,B®B¯
BºB ÁB
Á!B Á;BÁ<B Â BÂBÂ
B ÂB Â	BÂBÂBÂBc              ƒ   ó”   K  — 	 | j                  d«      ƒ d {  –—† S 7 Œ# t        $ r}t        d|› �«       i g dœcY d }~S d }~ww xY w­w)Nuu  () => {
            const monthRe = /(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+\d{1,2}\b/i;
            const hrRe = /([\d.]+)\s*hr\b/i;
            const hoursRe = /([\d.]+)\s*hours?\b/i;
            const usageRe = /([\d.]+)\s+of\s+([\d.]+)\s+hours?\s*\((\d+)%\)\s+used/i;
            const seasonRe = /^(\d{4}-\d{4})(?:\s+Season)?$/i;

            const leftCol =
                document.querySelector('.manage-library-left-column') ||
                document.querySelector('#rightColumnDiv');
            const rightCol =
                document.querySelector('.manage-library-right-column') ||
                document.querySelector('.ml__section')?.closest('.manage-library-right-column') ||
                document.querySelector('.ml__section');

            const leftText = leftCol?.innerText || '';
            const usageMatch = leftText.match(usageRe);

            const storage = {
                used_hours: usageMatch ? parseFloat(usageMatch[1]) : null,
                total_hours: usageMatch ? parseFloat(usageMatch[2]) : null,
                usage_percent: usageMatch ? parseInt(usageMatch[3], 10) : null,
                breakdown: [],
            };

            if (leftCol) {
                const lines = leftText.split('\n').map((l) => l.trim()).filter(Boolean);
                for (const line of lines) {
                    const hoursMatch = line.match(hoursRe);
                    if (!hoursMatch) continue;
                    const hours = parseFloat(hoursMatch[1]);
                    const label = line.replace(hoursMatch[0], '').trim();
                    if (!label || /storage hours|refresh|current video|deleted video|learn more/i.test(label)) continue;
                    storage.breakdown.push({ name: label, hours });
                }
            }

            const parseLabelMeta = (rawLabel, rowText) => {
                let label = (rawLabel || '').trim();
                let count = null;
                const countMatch = label.match(/\((\d+)\)\s*$/);
                if (countMatch) {
                    count = parseInt(countMatch[1], 10);
                    label = label.replace(/\(\d+\)\s*$/, '').trim();
                }
                const text = (rowText || label).replace(/\s+/g, ' ').trim();
                const dateMatch = text.match(monthRe);
                const hrMatch = text.match(hrRe);
                if (dateMatch) label = label.replace(dateMatch[0], '').trim();
                if (hrMatch) label = label.replace(hrMatch[0], '').trim();
                label = label.replace(/\s{2,}/g, ' ').trim();
                return {
                    label,
                    date: dateMatch ? dateMatch[0] : undefined,
                    hours: hrMatch ? parseFloat(hrMatch[1]) : undefined,
                    count,
                };
            };

            const parseJstreeLi = (li, path) => {
                const anchor = li.querySelector(':scope > a.jstree-anchor') || li.querySelector(':scope > a');
                const rowText = (li.innerText || '').replace(/\s+/g, ' ').trim();
                const hrMatch = rowText.match(/([\d.]+)\s*hr\b/i);
                let rawLabel = (anchor?.textContent || '').trim();
                if (!rawLabel) {
                    rawLabel = rowText.replace(/[\d.]+\s*hr\b/i, '').trim();
                }
                const meta = parseLabelMeta(rawLabel, rowText);
                if (hrMatch && meta.hours == null) {
                    meta.hours = parseFloat(hrMatch[1]);
                }
                if (meta.date) meta.label = meta.label.replace(meta.date, '').replace(/\s{2,}/g,' ').trim();
                const nodeId = li.id || anchor?.id || path;
                const seasonId = meta.label.match(/(\d{4}-\d{4})/)?.[1] || nodeId;
                return { id: seasonId, ...meta, children: [] };
            };

            let seasons = [];
            const libraryView = document.querySelector('#libraryView');
            const jstreeRoot =
                libraryView?.querySelector('ul.jstree-container-ul') ||
                rightCol?.querySelector('ul.jstree-container-ul') ||
                document.querySelector('ul.jstree-container-ul');
            if (jstreeRoot) {
                seasons = Array.from(jstreeRoot.querySelectorAll(':scope > li')).map((li, idx) =>
                    parseJstreeLi(li, String(idx))
                );
            }

            // Classic Manage Library table rows fallback
            if (seasons.length === 0 && rightCol) {
                const rows = rightCol.querySelectorAll('table tr, .ml__section tr');
                rows.forEach((row, idx) => {
                    const rowText = (row.innerText || '').replace(/\s+/g, ' ').trim();
                    if (!/season/i.test(rowText)) return;
                    const meta = parseLabelMeta(rowText, rowText);
                    if (!meta.label) return;
                    const seasonId = meta.label.match(/(\d{4}-\d{4})/)?.[1] || String(idx);
                    seasons.push({ id: seasonId, ...meta, children: [] });
                });
            }

            // Body lines fallback â€” Classic Manage Library lists seasons as text rows
            if (seasons.length === 0) {
                const lines = (rightCol?.innerText || document.body?.innerText || '')
                    .split('\n').map((l) => l.trim()).filter(Boolean);
                for (let i = 0; i < lines.length; i++) {
                    const m = lines[i].match(/^(\d{4}-\d{4})\s+Season$/i);
                    if (!m) continue;
                    let hours = null;
                    if (i + 1 < lines.length) {
                        const hrLine = lines[i + 1].match(/^([\d.]+)\s*hr$/i);
                        if (hrLine) hours = parseFloat(hrLine[1]);
                    }
                    seasons.push({
                        id: m[1],
                        label: `${m[1]} Season`,
                        hours,
                        children: [],
                    });
                }
            }

            // Sort seasons descending (newest first) like Hudl
            seasons.sort((a, b) => (a.id < b.id ? 1 : -1));

            return { storage, seasons };
        }zManage Library parse failed: )r!  Úseasons©rj   r0   r^   )r#   rm   s     r   r(  r(  Õ
  s^   è ø€ ðE.Ø—]‘]ð	óA
÷ A
ð A	
ð A
ùôD ò .ÜÐ-¨c¨UÐ3Ô4Ø¨"Ñ-Õ-ûð.üs:   ‚A„ ˜™ œA� Ÿ	A¨A ºA»AÁ AÁAuç  
(seasonLabel) => {
    const monthRe = new RegExp('(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+(\d{1,2})(?!\d)', 'i');
    const gluedMonthRe = new RegExp('(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)(\d{1,2})(?!\d)', 'i');
    const wordGluedMonthRe = new RegExp('[A-Za-z](Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+(\d{1,2})(?!\d)', 'i');
    const trailingMonthRe = /(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)$/i;
    const hrRe = /([\d.]+)\s*hr\b/i;
    const seasonYearRe = /(\d{4}-\d{4})/;
    const stripCount = (s) => (s || '').replace(/\s*\(\d+\)\s*$/, '').trim();
    const normalize = (s) =>
        stripCount((s || '').trim())
            .replace(/\s+Season$/i, '')
            .replace(hrRe, '')
            .replace(monthRe, '')
            .replace(gluedMonthRe, '')
            .replace(trailingMonthRe, '')
            .replace(/\s{2,}/g, ' ')
            .trim();
    const seasonKey = (s) => {
        const m = normalize(s).match(seasonYearRe);
        return m ? m[1] : normalize(s);
    };
    const getLiLabel = (li) => {
        const anchor = li.querySelector(':scope > a.jstree-anchor') || li.querySelector(':scope > a');
        if (anchor) return (anchor.textContent || '').trim();
        const clone = li.cloneNode(true);
        clone.querySelector('.toggler')?.remove();
        clone.querySelector('ul')?.remove();
        clone.querySelector('.jstree-ocl')?.remove();
        return (clone.textContent || '').replace(/\s+/g, ' ').trim();
    };
    const labelsMatch = (a, b) => {
        const na = normalize(a);
        const nb = normalize(b);
        if (!na || !nb) return false;
        if (na === nb) return true;
        const ya = seasonKey(a);
        const yb = seasonKey(b);
        if (ya && yb && ya === yb) return true;
        return na.includes(nb) || nb.includes(na);
    };
    const target = seasonKey(seasonLabel);
    const root =
        document.querySelector('#libraryView') ||
        document.querySelector('.manage-library-right-column') ||
        document.querySelector('.tree') ||
        document.body;
    const selectors = [
        '#libraryView ul.jstree-container-ul > li',
        '#libraryView > ul > li',
        '.tree > ul.t2 > li',
        '#libraryView ul.t2 > li',
        'ul.jstree-container-ul > li',
        'li.toggle',
        'li.jstree-node',
    ];
    let seasonLi = null;
    for (const sel of selectors) {
        for (const li of root.querySelectorAll(sel)) {
            const raw = getLiLabel(li);
            if (labelsMatch(raw, seasonLabel) || labelsMatch(raw, target)) {
                seasonLi = li;
                break;
            }
        }
        if (seasonLi) break;
    }
    if (!seasonLi) return { found: false, expanded: false, childCount: 0 };
    const childUl = seasonLi.querySelector(':scope > ul');
    const childCount = childUl ? childUl.querySelectorAll(':scope > li').length : 0;
    // Only treat as expanded when Hudl shows the open state â€” not merely because
    // children exist in the DOM while the row still displays "+".
    const expanded =
        seasonLi.classList.contains('jstree-open') ||
        (seasonLi.classList.contains('toggle') && seasonLi.classList.contains('expanded'));
    return { found: true, expanded, childCount };
}
Úseason_labelc              ƒ   óv   K  — 	 | j                  t        |«      ƒ d {  –—† S 7 Œ# t        $ r	 ddddœcY S w xY w­w)NFr   )r  ÚexpandedÚ
childCount)rj   Ú_SEASON_TREE_STATE_JSr0   ©r#   rË  s     r   Ú_get_season_tree_staterÑ  ®  sA   è ø€ ðDØ—]‘]Ô#8¸,ÓG×GÐGÐGùÜò DØ¨EÀÑCÒCðDüs(   ‚9„$ �"ž$ ¡9¢$ ¤6³9µ6¶9uú  
(seasonLabel) => {
    const seasonYearRe = /(\d{4}-\d{4})/;
    const stripCount = (s) => (s || '').replace(/\s*\(\d+\)\s*$/, '').trim();
    const normalize = (s) => stripCount(s).replace(/\s+Season$/i, '').replace(/[\d.]+\s*hr\b/i, '').replace(/\s{2,}/g, ' ').trim();
    const seasonKey = (s) => {
        const m = normalize(s).match(seasonYearRe);
        return m ? m[1] : normalize(s);
    };
    const labelsMatch = (a, b) => {
        const na = normalize(a);
        const nb = normalize(b);
        if (!na || !nb) return false;
        if (na === nb) return true;
        const ya = seasonKey(a);
        const yb = seasonKey(b);
        return Boolean(ya && yb && ya === yb);
    };
    const getLiLabel = (li) => {
        const anchor = li.querySelector(':scope > a.jstree-anchor') || li.querySelector(':scope > a');
        if (anchor) return (anchor.textContent || '').trim();
        const clone = li.cloneNode(true);
        clone.querySelector('.toggler')?.remove();
        clone.querySelector('ul')?.remove();
        clone.querySelector('.jstree-ocl')?.remove();
        return (clone.textContent || '').replace(/\s+/g, ' ').trim();
    };
    const flash = (li) => {
        const prev = li.getAttribute('style') || '';
        li.setAttribute('style', prev + ';outline:3px solid #0066cc!important;background:#c5ddf3!important;');
        setTimeout(() => { try { li.setAttribute('style', prev); } catch (_) {} }, 2500);
    };
    // TOP-LEVEL seasons only â€” never match nested folders under another year.
    const topLists = [
        ...document.querySelectorAll('.tree ul.t2'),
        ...document.querySelectorAll('#libraryView ul.t2'),
        ...document.querySelectorAll('#libraryView > ul'),
        ...document.querySelectorAll('.manage-library-right-column ul.jstree-container-ul'),
        ...document.querySelectorAll('ul.jstree-container-ul'),
    ];
    const seen = new Set();
    const candidates = [];
    for (const ul of topLists) {
        for (const li of Array.from(ul.children || [])) {
            if (!(li instanceof HTMLElement) || li.tagName !== 'LI' || seen.has(li)) continue;
            seen.add(li);
            candidates.push(li);
        }
    }
    const targetYear = seasonKey(seasonLabel);
    let seasonLi = null;
    for (const li of candidates) {
        const raw = getLiLabel(li);
        if (labelsMatch(raw, seasonLabel) || seasonKey(raw) === targetYear) {
            seasonLi = li;
            break;
        }
    }
    if (!seasonLi) return { ok: false, reason: 'season_not_found', scanned: candidates.length };
    try { seasonLi.scrollIntoView({ block: 'center', inline: 'nearest' }); } catch (_) {}
    flash(seasonLi);

    const isOpen =
        seasonLi.classList.contains('jstree-open') ||
        (seasonLi.classList.contains('toggle') && seasonLi.classList.contains('expanded'));

    const anchor = seasonLi.querySelector(':scope > a.jstree-anchor, :scope > a');
    let clicked = 'flash';
    if (!isOpen) {
        const ocl = seasonLi.querySelector(':scope > .jstree-ocl');
        const toggler = seasonLi.querySelector(':scope > span .toggler, :scope > .toggler, :scope > span > .toggler');
        if (ocl) { ocl.click(); clicked = 'ocl'; }
        else if (toggler) { toggler.click(); clicked = 'toggler'; }
        else if (anchor) { anchor.click(); clicked = 'anchor'; }
        else { seasonLi.click(); clicked = 'li'; }
    }

    // Keep the opened year under the red banner (do not leave viewport on 2026-2027).
    try { seasonLi.scrollIntoView({ block: 'center', inline: 'nearest' }); } catch (_) {}
    const childUl = seasonLi.querySelector(':scope > ul');
    const childCount = childUl ? childUl.querySelectorAll(':scope > li').length : 0;
    return { ok: true, alreadyOpen: isOpen, clicked, childCount, year: targetYear };
}
c           	   ƒ   óD
  K  — 	 t        | |g «      ƒ d{  –—† }|j                  d«      r…t        d«      D ]q  }t        | |«      ƒ d{  –—† }|j                  d«      s|j                  dd«      dkD  rdd|j                  dd«      d	œc S t	        j
                  d
«      ƒ d{  –—†  Œs dddd	œS 	 t        j                  d|«      }|r|j                  d«      n|j                  «       }	 | j                  d|«      ƒ d{  –—† }|j                  d«      �rÛ| j                  d|› d�«      j                  }	|	j                  «       ƒ d{  –—†  t	        j
                  d
«      ƒ d{  –—†  |j                  d«      s­|	j                  d«      j                  }
|
j!                  «       ƒ d{  –—† dkD  r-|
j#                  dd¬«      ƒ d{  –—†  t        d|› d|› �«       n,|	j#                  dd¬«      ƒ d{  –—†  t        d|› d|› �«       t	        j
                  d«      ƒ d{  –—†  nt        d|› d�«       |	j                  «       ƒ d{  –—†  t	        j
                  d«      ƒ d{  –—†  t        d«      D ]q  }t        | |«      ƒ d{  –—† }|j                  d«      s|j                  dd«      dkD  rdd|j                  dd«      d	œc S t	        j
                  d
«      ƒ d{  –—†  Œs dddd	œS 	 	 | j                  t$        |«      ƒ d{  –—† }|j                  d«      sVt)        | |«      ƒ d{  –—† }|st+        d!|› d"�«      ‚t        d|› d#�«       t	        j
                  d«      ƒ d{  –—†  dd$d%œS |j                  d&«      rAt        d|› d'|j                  dd«      › d(�«       t	        j
                  d«      ƒ d{  –—†  |S t        d|› d)|j                  d*«      › �«       t	        j
                  d+«      ƒ d{  –—†  t        d«      D ]]  }t        | |«      ƒ d{  –—† }|j                  d«      s|j                  dd«      dkD  r|c S t	        j
                  d
«      ƒ d{  –—†  Œ_ |S 7 �Œ`7 �Œ07 �ŒØ# t        $ r}t        d|› �«       Y d}~�Œêd}~ww xY w7 �Œ£7 �Œ]7 �ŒC7 �Œ7 �Œç7 �Œ½7 �Œ’7 �Œm7 �ŒS7 �Œ47 �ŒÜ# t        $ r}t        d|› �«       Y d}~�Œîd}~ww xY w7 �ŒÜ# t        $ r}dt'        |«      d œ}Y d}~�Œ÷d}~ww xY w7 �Œß7 �Œ¥7 �ŒS7 �Œ7 Œ÷7 Œ²­w),zKClick the exact season year on Hudl so the headed browser mirrors frontend.Nrä   r  rÍ  rÎ  r   TÚclassic)rä   r‘  rÎ  rÜ  zClassic season click failed: ú(\d{4}-\d{4})r   aø  (year) => {
                const getLabel = (li) => {
                    const a = li.querySelector(':scope > a.jstree-anchor, :scope > a');
                    if (a) return (a.textContent || '').trim();
                    const clone = li.cloneNode(true);
                    clone.querySelector('ul')?.remove();
                    clone.querySelector('.toggler')?.remove();
                    return (clone.textContent || '').replace(/\s+/g, ' ').trim();
                };
                const candidates = Array.from(document.querySelectorAll(
                    '#libraryView li.toggle, .tree li.toggle, .tree ul.t2 > li, #libraryView ul.t2 > li'
                ));
                let best = null;
                let bestDepth = Infinity;
                for (const li of candidates) {
                    const label = getLabel(li);
                    if (!label.includes(year)) continue;
                    if (!/\d{4}-\d{4}/.test(label) && !/Season/i.test(label)) continue;
                    let depth = 0;
                    let p = li.parentElement;
                    while (p) {
                        if (p.matches && p.matches('li.toggle, li.jstree-node')) depth += 1;
                        p = p.parentElement;
                    }
                    if (depth < bestDepth) {
                        bestDepth = depth;
                        best = li;
                    }
                }
                if (!best) return { found: false };
                best.setAttribute('data-sergio-season', year);
                return {
                    found: true,
                    label: getLabel(best),
                    open: best.classList.contains('expanded') || best.classList.contains('jstree-open'),
                };
            }r  zli[data-sergio-season="r5  ÚopenzY:scope > span .toggler, :scope > .toggler, :scope > .jstree-ocl, :scope > span > .togglerr^  r‡  úSeason 'z': Playwright clicked + for z': Playwright clicked row for çš™™™™™á?u&   ': already open â€” scrolled into viewr  é   Úplaywright_locatorz(Season Playwright locator click failed: F©rä   ÚreasonzCould not find season 'z' on Manage Library pagez': clicked via locator fallbackÚfallback)rä   r‘  ÚalreadyOpenz': focused on Hudl (z children, already open)z': clicked on Hudl via r‘  gÍÌÌÌÌÌÜ?)Ú_classic_mark_and_clickr´   r…  rÑ  r<  rÃ  r0   r^   r   r   r   r*   rj   r,   r-   rR  r.   r  Ú_CLICK_EXPAND_SEASON_JSrd   Ú_click_manage_library_seasonrT   )r#   rË  r  r  rE   rm   Ú
year_matchÚyearr(  rÇ  Útogglerr‘  s               r   Ú_click_and_expand_seasonrä    sé  è ø€ ð5Ü.¨t°\À2ÓF×FˆØ�:‰:�dÔÜ˜2–Y�Ü4°T¸<ÓH×H�Ø—9‘9˜ZÔ(¨E¯I©I°lÀAÓ,FÈÒ,Jà"Ø#,Ø&+§i¡i°¸aÓ&@ñò ô
 —m‘m CÓ(×(Ñ(ð ð ¨9ÀAÑFÐFð ô —‘Ð+¨\Ó:€JÙ",ˆ:×Ñ˜AÔ°,×2DÑ2DÓ2F€DðI@ØŸ™ð$ðJ óM'
÷ '
ˆðP �;‰;�wÕØ—\‘\Ð$;¸D¸6ÀÐ"DÓE×KÑKˆFØ×3Ñ3Ó5×5Ð5Ü—-‘- Ó$×$Ð$Ø—;‘;˜vÔ&Ø Ÿ.™.ðEó÷ ‘%ð ð !Ÿ™›×(¨1Ò,Ø!Ÿ-™-°¸D˜-ÓA×AÐAÜ˜H \ NÐ2NÈtÈfÐUÕVà Ÿ,™,¨t¸4˜,Ó@×@Ð@Ü˜H \ NÐ2PÐQUÐPVÐWÔXÜ—m‘m DÓ)×)Ñ)ä˜  Ð.TÐUÔVà×3Ñ3Ó5×5Ð5Ü—-‘- Ó%×%Ð%Ü˜2–Y�Ü4°T¸<ÓH×H�Ø—9‘9˜ZÔ(¨E¯I©I°lÀAÓ,FÈÒ,Jà"Ø#7Ø&+§i¡i°¸aÓ&@ñò ô
 —m‘m CÓ(×(Ñ(ð ð Ð+?ÈqÑQÐQð=  ðF3Ø—}‘}Ô%<¸lÓK×Kˆð �:‰:�dÔÜ4°T¸<ÓH×HˆÙÜÐ6°|°nÐD\Ð]Ó^Ð^Ü�˜˜Ð&EÐFÔGÜ�m‰m˜DÓ!×!Ð!Ø zÑ2Ð2à‡z�z�-Ô ÜØ�|�nð %Ø—
‘
˜<¨Ó+Ð,Ð,DðFô	
ô �m‰m˜DÓ!×!Ð!Øˆä	ˆH�\�NÐ"9¸&¿*¹*ÀYÓ:OÐ9PÐ
QÔRÜ
�-‰-˜Ó
×ÐÜ�2ŽYˆÜ,¨T°<Ó@×@ˆØ�9‰9�ZÔ  E§I¡I¨l¸AÓ$>ÀÒ$BØŠMÜ�m‰m˜CÓ × Ñ ð	 ð
 €Mðy Gùð Iùð )úäò 5ÜÐ-¨c¨UÐ3×4Ò4ûð5úð'
ùðT 6ùØ$ùð )ùØAùð Aùà)ùð 6ùØ%ùàHùð )úäò @ÜÐ8¸¸Ð>×?Ò?ûð@úð
 LúÜò 3Ø¬¨S«Ñ2Žûð3úð Iùð 	"ùð 	"ùð ùà@øð 	!ús  ‚T „Q= ”Q4•2Q= ÁQ7ÁAQ= Â	T Â
Q= Â"Q:Â#Q= Â.;T Ã*S Ã?R"Ä AS ÅR%Å	S Å%R(Å&AS Æ)R+Æ*S ÇR.Ç,S Ç4R1Ç5-S È"R4È#'S É
R7ÉS É'R:É(!S Ê	R=Ê
AS ËT ËS Ë$S Ë%S Ë0T Ë3S+ ÌS(ÌS+ Ì T Ì1TÌ2<T Í.TÍ/AT ÏTÏ>T ÐTÐ!T Ð$TÐ%AT Ñ+TÑ,T Ñ4Q= Ñ7Q= Ñ:Q= Ñ=	RÒRÒT ÒRÒT Ò"S Ò%S Ò(S Ò+S Ò.S Ò1S Ò4S Ò7S Ò:S Ò=S Ó S Ó	S%ÓS ÓT Ó S%Ó%T Ó(S+ Ó+	TÓ4TÔT ÔTÔT ÔT ÔT ÔT ÔT ÔT r�   c              ƒ   óL  K  — t        | |«      ƒ d{  –—† }|j                  d«      r|j                  d«      ryt        j                  d|«      }|r|j	                  d«      nd}d|› d�d	|› d�d
|› d�g}|r|j                  d|› d�d|› d�g«       |D �]Q  }	 | j                  |«      j                  }|j                  «       ƒ d{  –—† dkD  �rd|v r|n|j                  d«      j                  }|j                  «       ƒ d{  –—† dk(  r|}|j                  d«      j                  }	|	j                  «       ƒ d{  –—† dkD  r?|j                  d«      ƒ d{  –—† xs d}
d|
v r|	j                  d¬«      ƒ d{  –—†   y y|j                  d«      ƒ d{  –—† xs d}
d|
v r|j                  d¬«      ƒ d{  –—†   yd|
v sd|
v r y|j                  d¬«      ƒ d{  –—†   y�ŒT 	 |xs |}| j                  d|› d�«      j                  }|j                  «       ƒ d{  –—† dkD  rÈ|j                  d«      ƒ d{  –—† xs d}
d|
v sd|
v ry|j                  d«      j                  }	|	j                  «       ƒ d{  –—† dkD  r|	j                  d¬«      ƒ d{  –—†  y|j                  d«      j                  }|j                  «       ƒ d{  –—† dkD  r|j                  d¬«      ƒ d{  –—†  y	 | j                  |d¬«      j                  }|j                  «       ƒ d{  –—† dkD  r|j                  d¬«      ƒ d{  –—†  y	 y7 �Œ77 �Œ„7 �ŒJ7 �Œ7 �Œü7 �ŒÝ7 �ŒÃ7 �Œ¤7 �Œ�# t        $ r Y �ŒÛw xY w7 �ŒQ7 �Œ87 Œú7 Œß7 Œ­7 Œ’# t        $ r Y Œšw xY w7 Œl7 ŒQ# t        $ r Y yw xY w­w)zGClick/expand a season row on Classic Manage Library (locator fallback).Nr  rÍ  TrÔ  r   z5.manage-library-right-column .jstree-anchor:text-is("rD  z6.manage-library-right-column .jstree-anchor:has-text("z&.manage-library-right-column >> text="rC  ú=.manage-library-right-column li:has(.jstree-anchor:has-text("ú"))ú li:has(.jstree-anchor:has-text("r   zli:zxpath=ancestor-or-self::li[1]ú:scope > .jstree-oclrd  r&   újstree-closedr^  rq   Ú	collapsedújstree-openz2:scope > .jstree-ocl, .jstree-closed > .jstree-oclú.jstree-anchorr§  F)rÑ  r´   r   r   r   rè   r,   r-   r.   r/   r  r0   r­  )r#   r�   rE   rá  râ  r¼  r?  r  ÚliÚoclÚclassesr   Ú	season_liÚanchors                 r   rà  rà  Ž  s¥  è ø€ ä(¨¨uÓ5×5€EØ‡y�y�Ô˜eŸi™i¨
Ô3Øä—‘Ð+¨UÓ3€JÙ",ˆ:×Ñ˜AÔ°$€Dð @À¸wÀbÐIØ
@ÀÀÀrÐJØ
0°°°qÐ9ð€Iñ
 Ø×ÑØKÈDÈ6ÐQTÐUØ.¨t¨f°CÐ8ð
ô 	ô
 ˆð	Ø—,‘,˜sÓ#×)Ñ)ˆCØ—Y‘Y“[×  1Ó$Ø! S™L‘S¨c¯k©kÐ:YÓ.Z×.`Ñ.`�ØŸ™›×# qÒ(Ø�BØ—j‘jÐ!7Ó8×>Ñ>�ØŸ™›×$ qÒ(Ø%'×%5Ñ%5°gÓ%>×>ÒEÀ2�GØ&¨'Ñ1Ø!Ÿi™i°˜iÓ5×5Ð5Ù#ÙØ!#×!1Ñ!1°'Ó!:×:ÒA¸r�Ø 'Ñ)ØŸ)™)¨D˜)Ó1×1Ð1ÙØ Ñ(¨M¸WÑ,DÙØ—i‘i¨�iÓ-×-Ð-Ùñ% %ð ð2Ø’˜ˆØ—L‘LÐ#CÀFÀ8È3Ð!OÓP×VÑVˆ	Ø—‘Ó"×" QÒ&Ø&×4Ñ4°WÓ=×=ÒDÀ"ˆGØ Ñ'¨:¸Ñ+@ØØ×#Ñ#Ð$XÓY×_Ñ_ˆCØ—Y‘Y“[×  1Ò$Ø—i‘i¨�iÓ-×-Ð-ØØ×&Ñ&Ð'7Ó8×>Ñ>ˆFØ—\‘\“^×# aÒ'Ø—l‘l¨4�lÓ0×0Ð0ØðØ×Ñ˜u¨DÐÓ1×7Ñ7ˆØ—‘“×˜qÒ Ø—)‘) D�)Ó)×)Ð)Øð !ð ðK 6ùð* !ùà#ùð %ùØ>ùà5ùð ;ùà1ùð .úäò 	Úð	úð #ùØ=ùð !øØ-øð $øØ0ùäò Ùðúð
 øØ)ùäò ØàðüsÞ  ‚N$’M	“BN$Â.M$ÃMÃ	<M$ÄMÄ7M$Ä=MÄ>M$ÅMÅ!M$Å;MÅ<M$Æ N$ÆM$ÆMÆ!M$Æ:MÆ;M$Æ?N$ÇM$Ç	N$ÇM$Ç M!Ç!M$Ç%N$Ç+8N È#M4È$N È?M7É N ÉN$É.N É?M:Ê N ÊM<ÊN Ê!N$Ê".N ËM>ËN Ë-N Ë.N Ë2N$Ë40N Ì$NÌ%N ÍNÍN ÍN$ÍN$ÍM$ÍM$ÍM$ÍM$ÍM$ÍM$ÍM$Í!M$Í$	M1Í-N$Í0M1Í1N$Í4N Í7N Í:N Í<N Í>N Î N Î	NÎN$ÎNÎN$ÎN ÎN Î	N!ÎN$Î N!Î!N$c              ƒ   ó�   K  — 	 | j                  d|«      ƒ d{  –—† S 7 Œ# t        $ r}t        d|› �«       g cY d}~S d}~ww xY w­w)zPParse folder/video rows under an expanded season (jstree, table, or text lines).aÈ'  (seasonLabel) => {
                const monthRe = new RegExp('(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+(\d{1,2})(?!\d)', 'i');
                const gluedMonthRe = new RegExp('(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)(\d{1,2})(?!\d)', 'i');
                const trailingMonthRe = /(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)$/i;
                const hrRe = /([\d.]+)\s*hr\b/i;
                const seasonRe = /^\d{4}-\d{4}\s+Season$/i;
                const norm = (s) => (s || '').replace(/\s+Season$/i, '').trim();

                const extractDate = (text) => {
                    const t = (text || '').replace(/\s+/g, ' ').trim().split(hrRe)[0].trim();
                    const full = t.match(monthRe);
                    if (full) return `${full[1]} ${full[2]}`;
                    const glued = t.match(gluedMonthRe);
                    if (glued) return `${glued[1]} ${glued[2]}`;
                    const wordGlued = t.match(new RegExp('[A-Za-z](Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+(\d{1,2})(?!\d)', 'i'));
                    if (wordGlued) return `${wordGlued[1]} ${wordGlued[2]}`;
                    const trailing = t.match(trailingMonthRe);
                    if (trailing) return trailing[0];
                    return undefined;
                };

                const stripDates = (text) => {
                    let value = (text || '').replace(/\s+/g, ' ').trim();
                    value = value
                        .replace(monthRe, '')
                        .replace(gluedMonthRe, '')
                        .replace(/([A-Za-z])(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)/gi, '$1')
                        .replace(trailingMonthRe, '')
                        .replace(hrRe, '');
                    return value.replace(/\s{2,}/g, ' ').trim();
                };

                const isFolderLi = (li) => {
                    if (li.classList.contains('toggle')) return true;
                    if (li.classList.contains('jstree-closed') || li.classList.contains('jstree-open')) return true;
                    if (li.querySelector(':scope > .toggler, :scope > span .toggler')) return true;
                    if (li.querySelector(':scope > .jstree-ocl')) return true;
                    return false;
                };

                const cleanLabel = (label, rowText) => {
                    const text = (rowText || label || '').replace(/\s+/g, ' ').trim();
                    let value = stripDates(label);
                    const hrMatch = text.match(hrRe);
                    if (hrMatch) value = value.replace(hrMatch[0], '').trim();
                    return value.replace(/\s{2,}/g, ' ').trim();
                };

                const parseMeta = (label, rowText) => {
                    const text = (rowText || label || '').replace(/\s+/g, ' ').trim();
                    let name = cleanLabel(label, rowText);
                    let count = null;
                    const countMatch = name.match(/\((\d+)\)\s*$/);
                    if (countMatch) {
                        count = parseInt(countMatch[1], 10);
                        name = name.replace(/\(\d+\)\s*$/, '').trim();
                    }
                    const date = extractDate(text);
                    const hrMatch = text.match(hrRe);
                    return {
                        label: name,
                        date,
                        hours: hrMatch ? parseFloat(hrMatch[1]) : undefined,
                        count,
                    };
                };

                const getLiLabel = (li) => {
                    const anchor = li.querySelector(':scope > a.jstree-anchor') || li.querySelector(':scope > a');
                    if (anchor) return (anchor.textContent || '').trim();
                    const clone = li.cloneNode(true);
                    clone.querySelector('.toggler')?.remove();
                    clone.querySelector('ul')?.remove();
                    return (clone.textContent || '').replace(/\s+/g, ' ').trim();
                };

                const parseJstreeLi = (li, path) => {
                    const rowText = (li.innerText || '').replace(/\s+/g, ' ').trim();
                    const meta = parseMeta(getLiLabel(li), rowText);
                    const nodeId =
                        li.id ||
                        li.getAttribute('data-cat-id') ||
                        li.getAttribute('data-categoryid') ||
                        path;
                    const isLeaf =
                        !isFolderLi(li) &&
                        (li.classList.contains('jstree-leaf') ||
                            Boolean(li.querySelector('.jstree-checkbox, input[type="checkbox"]')));
                    if (isLeaf) {
                        return { id: String(nodeId), ...meta, selectable: true, children: [] };
                    }
                    return { id: String(nodeId), ...meta, children: [] };
                };

                const rightCol =
                    document.querySelector('.manage-library-right-column') ||
                    document.querySelector('.ml__section') ||
                    document.body;

                // 1) jstree under season node
                const hrRe2 = /([\d.]+)\s*hr\b/i;
                const seasonYearRe = /(\d{4}-\d{4})/;
                const normalize = (s) => (s || '').replace(/\s+Season$/i, '').replace(hrRe2, '').replace(monthRe, '').replace(/\s*\(\d+\)\s*$/, '').replace(/\s{2,}/g, ' ').trim();
                const seasonKey = (s) => {
                    const m = normalize(s).match(seasonYearRe);
                    return m ? m[1] : normalize(s);
                };
                const target = seasonKey(seasonLabel);
                const anchors = Array.from(document.querySelectorAll('.jstree-anchor'));
                const seasonAnchor = anchors.find((a) => {
                    const raw = (a.textContent || '').trim();
                    return seasonKey(raw) === target || normalize(raw) === normalize(seasonLabel);
                });
                if (seasonAnchor) {
                    const seasonLi = seasonAnchor.closest('li');
                    const childUl = seasonLi?.querySelector(':scope > ul');
                    if (childUl) {
                        return Array.from(childUl.querySelectorAll(':scope > li')).map((li, idx) =>
                            parseJstreeLi(li, `${seasonLabel}-${idx}`)
                        );
                    }
                }

                const toggleLis = Array.from(
                    document.querySelectorAll('#libraryView li.toggle, .tree li.toggle, .tree ul.t2 > li')
                );
                const seasonToggle = toggleLis.find((li) => {
                    const raw = getLiLabel(li);
                    return seasonKey(raw) === target || normalize(raw) === normalize(seasonLabel);
                });
                if (seasonToggle) {
                    const childUl = seasonToggle.querySelector(':scope > ul');
                    if (childUl) {
                        return Array.from(childUl.querySelectorAll(':scope > li')).map((li, idx) =>
                            parseJstreeLi(li, `${seasonLabel}-toggle-${idx}`)
                        );
                    }
                }

                // 2) Table rows after season header
                const children = [];
                const rows = Array.from(rightCol.querySelectorAll('tr'));
                let pastSeason = false;
                for (const row of rows) {
                    const rowText = (row.innerText || '').replace(/\s+/g, ' ').trim();
                    if (!rowText) continue;
                    if (rowText === seasonLabel || rowText.startsWith(seasonLabel)) {
                        pastSeason = true;
                        continue;
                    }
                    if (pastSeason && seasonRe.test(rowText)) break;
                    if (!pastSeason || seasonRe.test(rowText)) continue;
                    const meta = parseMeta(rowText.replace(hrRe, '').replace(monthRe, ''), rowText);
                    if (!meta.label || /current video/i.test(meta.label)) continue;
                    children.push({ id: `${seasonLabel}-${children.length}`, ...meta, children: [] });
                }
                if (children.length) return children;

                // 3) Plain text lines (Hudl Classic fallback)
                const lines = (rightCol.innerText || '').split('\n').map((l) => l.trim()).filter(Boolean);
                pastSeason = false;
                for (let i = 0; i < lines.length; i++) {
                    const line = lines[i];
                    if (line === seasonLabel || line === seasonLabel.replace(' Season', '')) {
                        pastSeason = true;
                        continue;
                    }
                    if (pastSeason && seasonRe.test(line)) break;
                    if (!pastSeason || seasonRe.test(line)) continue;
                    if (/^current video$/i.test(line)) continue;
                    if (/^[\d.]+\s*hr$/i.test(line)) continue;

                    const combined = [line, lines[i + 1] || '', lines[i + 2] || ''].join(' ');
                    let meta = parseMeta(line, combined);

                    if (!meta.date && lines[i + 1] && monthRe.test(lines[i + 1])) {
                        meta.date = lines[i + 1].match(monthRe)[0];
                        i += 1;
                        if (lines[i + 1] && hrRe.test(lines[i + 1])) {
                            meta.hours = parseFloat(lines[i + 1].match(hrRe)[1]);
                            i += 1;
                        }
                    } else if (!meta.hours && lines[i + 1] && hrRe.test(lines[i + 1])) {
                        meta.hours = parseFloat(lines[i + 1].match(hrRe)[1]);
                        i += 1;
                    }

                    if (!meta.label) continue;
                    children.push({ id: `${seasonLabel}-${children.length}`, ...meta, children: [] });
                }
                return children;
            }NzSeason children parse failed: rÊ  )r#   rË  rm   s      r   Ú _parse_season_children_from_pagerô  Ø  s_   è ø€ ðFØ—]‘]ðð@ óCB
÷ B
ð B	
ð B
ùôF ò ÜÐ.¨s¨eÐ4Ô5Ø�	ûðüs8   ‚A„  ™š  �Až   	A©>¸A¹A¾AÁAc              ƒ   ó6   K  — t        | |«      ƒ d {  –—† S 7 Œ­wrÉ   )rô  rÐ  s     r   Ú_parse_jstree_season_childrenrö  £  s   è ø€ Ü1°$¸ÓE×EÐEÐEús   ‚’“c              ƒ   óÞ  K  — 	 | j                  d|› d�«      j                  }|j                  «       ƒ d{  –—† dk(  ryd}||k  rz|j                  d«      }|j                  «       ƒ d{  –—† dk(  ry|j                  j                  d¬«      ƒ d{  –—†  |dz  }t	        j
                  d	«      ƒ d{  –—†  ||k  rŒyyy7 ŒŠ7 ŒX7 Œ27 Œ# t        $ r}t        d
|› �«       Y d}~yd}~ww xY w­w)z=Expand collapsed jstree folders under an already-open season.rè  rç  Nr   rÀ  rÁ  rq   r   rÂ  zNested season expand failed: )r,   r-   r.   r  r<  rÃ  r0   r^   )r#   rË  r¾  rñ  rÄ  Úclosedrm   s          r   Ú_expand_season_nested_nodesrù  §  sæ   è ø€ ð5Ø—L‘LÐ#CÀLÀ>ÐQTÐ!UÓV×\Ñ\ˆ	Ø—‘Ó"×" aÒ'ØØˆØ�zÒ!Ø×&Ñ&Ð'EÓFˆFØ—\‘\“^×# qÒ(ØØ—,‘,×$Ñ$¨TÐ$Ó2×2Ð2Ø�a‰KˆFÜ—-‘- Ó%×%Ð%ð �zÕ!ð #øð
 $øà2øà%ùÜò 5ÜÐ-¨c¨UÐ3×4Ñ4ûð5üsŠ   ‚C-„2C	 ¶C·C	 ¾C-¿+C	 Á*CÁ+C	 Á2C-Á3C	 ÂCÂ!C	 Â4CÂ5	C	 Â?C-ÃC	 ÃC	 ÃC	 ÃC	 Ã		C*ÃC%Ã C-Ã%C*Ã*C-c              ƒ   óÄ   K  — t        | |«      ƒ d {  –—† }|r|S 	 | j                  d|«      ƒ d {  –—† S 7 Œ#7 Œ# t        $ r}t        d|› �«       g cY d }~S d }~ww xY w­w)NaÇ  (seasonLabel) => {
                const monthRe = /(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+\d{1,2}\b/i;
                const hrRe = /([\d.]+)\s*hr\b/i;
                const children = [];
                const seen = new Set();

                const isSeasonRow = (text) => /^\d{4}-\d{4}(\s+Season)?$/i.test(text.trim());

                const rows = Array.from(document.querySelectorAll(
                    '.manage-library-right-column li, .manage-library-right-column tr, .jstree-anchor'
                ));

                let pastSeason = false;
                for (const el of rows) {
                    const text = (el.textContent || '').trim().replace(/\s+/g, ' ');
                    if (!text || text.length > 120) continue;

                    if (text === seasonLabel || text === seasonLabel.replace(' Season', '')) {
                        pastSeason = true;
                        continue;
                    }
                    if (pastSeason && isSeasonRow(text)) break;

                    if (!pastSeason) continue;
                    if (isSeasonRow(text)) continue;
                    if (seen.has(text)) continue;

                    const dateMatch = text.match(monthRe);
                    const hrMatch = text.match(hrRe);
                    const countMatch = text.match(/\((\d+)\)\s*$/);

                    seen.add(text);
                    children.push({
                        id: `${seasonLabel}-${children.length}`,
                        label: countMatch ? text.replace(/\(\d+\)\s*$/, '').trim() : text,
                        date: dateMatch ? dateMatch[0] : undefined,
                        hours: hrMatch ? parseFloat(hrMatch[1]) : undefined,
                        count: countMatch ? parseInt(countMatch[1], 10) : undefined,
                        children: [],
                    });

                    if (children.length >= 40) break;
                }

                return children;
            }zSeason expand parse failed: )rö  rj   r0   r^   )r#   rË  Úchildrenrm   s       r   Ú_parse_expanded_season_childrenrü  ¹  sw   è ø€ Ü2°4¸ÓF×F€HÙØˆð4Ø—]‘]ð-ð\ ó_0
÷ 0
ð 0	
ð Gøð
0
ùôb ò ÜÐ,¨S¨EÐ2Ô3Ø�	ûðüsF   ‚A ’6“A œ: ±8²: µA ¸: º	AÁAÁAÁA ÁAÁA rƒ  r„  c             ƒ   óÜ  K  — ddl m}m}  || «      ƒ d{  –—† r2t        d«       t	        | «      ƒ d{  –—†  t        | «      ƒ d{  –—†  y || «      ƒ d{  –—† ry|r1	 | j                  |dd¬«      ƒ d{  –—†  t        dd	«      ƒ d{  –—†   || «      ƒ d{  –—† r2t        d«       t	        | «      ƒ d{  –—†  t        | «      ƒ d{  –—†  y || «      ƒ d{  –—† rt        d| j                  › d�«       yt        d«       t	        | «      ƒ d{  –—†  t        | «      ƒ d{  –—†  y7 �Œ7 Œý7 Œì7 ŒÝ7 Œ¿7 Œ­# t        $ r}t        d
|› �«       Y d}~ŒÇd}~ww xY w7 ŒÃ7 Œ§7 Œ–7 Œ‡7 ŒQ7 Œ@­w)u2  Ensure Playwright browser is logged into Hudl before automation.

    Blank / about:blank lane tabs are NOT treated as logged-out â€” cookies live on
    the shared browser context. Prefer probing a Hudl URL (or the current page)
    and only run a full login if Hudl actually shows the identity form.
    r   )Úis_hudl_logged_inÚis_login_pageNuJ   Hudl session expired â€” running login before Manage Library automation...r=   rC   r…  rv   rw   z%Manage Library session probe failed: z&Hudl session reused via cookies (page=r.  uM   Hudl session not active â€” running login before Manage Library automation...)
rç   rþ  rÿ  r^   r;  r   rQ   r   r0   r   )r#   r„  rþ  rÿ  rm   s        r   r�  r�  õ  sj  è ø€ ÷ Lá˜4Ó × Ð ÜÐZÔ[Ü˜‹o×ÐÜ Ó%×%Ð%Øá˜tÓ$×$Ð$Øñ ð	AØ—)‘)˜IÐ2DÈe�)ÓT×TÐTÜ˜c 3Ó'×'Ð'ñ ˜4Ó × Ð ÜÐZÔ[Ü˜‹o×ÐÜ Ó%×%Ð%Øá˜tÓ$×$Ð$ÜÐ6°t·x±x°jÀÐBÔCØô 
Ð
YÔZÜ
�D‹/×ÐÜ
˜DÓ
!×!Ñ!ð? !ùàøØ%øð %øð UøØ'ùÜò 	AÜÐ9¸#¸Ð?×@Ñ@ûð	Aúð !øàøØ%øð %øð øØ!ús÷   ‚E,–D/—E,´D2µE,ÁD4ÁE,ÁD6ÁE,Á!D< Á8D8Á9D< ÂD:ÂD< ÂE,ÂE ÂE,Â:E"Â;E,ÃE$ÃE,ÃE&Ã7E,ÄE(ÄE,Ä)E*Ä*E,Ä2E,Ä4E,Ä6E,Ä8D< Ä:D< Ä<	EÅEÅE,ÅEÅE,Å"E,Å$E,Å&E,Å(E,Å*E,c              ƒ   ó`   K  — 	 | j                  «       ƒ d{  –—†  y7 Œ# t        $ r Y yw xY w­w)zCMake the Playwright Hudl window visible so demo clicks are obvious.N)Úbring_to_frontr0   )r#   s    r   Ú_bring_hudl_page_to_frontr     s.   è ø€ ðØ×!Ñ!Ó#×#Ò#ùÜò Ùðüs(   ‚.„ —˜ œ.� Ÿ	+¨.ª+«.uÚ  
({ seasonLabel, folderPath, collapseOthers }) => {
    const seasonYearRe = /(\d{4}-\d{4})/;
    const stripCount = (s) => (s || '').replace(/\s*\(\d+\)\s*$/, '').trim();
    const normalize = (s) =>
        stripCount(s)
            .replace(/\s+Season$/i, '')
            .replace(/[\d.]+\s*hr\b/i, '')
            .replace(/(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+\d{1,2}\b/gi, '')
            .replace(/\s{2,}/g, ' ')
            .trim();
    const seasonKey = (s) => {
        const m = normalize(s).match(seasonYearRe);
        return m ? m[1] : normalize(s);
    };
    const getLabel = (li) => {
        const a = li.querySelector(':scope > a.jstree-anchor, :scope > a, :scope > span > a');
        if (a) return (a.textContent || '').trim();
        const clone = li.cloneNode(true);
        clone.querySelectorAll('ul, .toggler, .jstree-ocl').forEach((n) => n.remove());
        return (clone.textContent || '').replace(/\s+/g, ' ').trim();
    };
    const directToggler = (li) =>
        li.querySelector(
            ':scope > .toggler, :scope > span > .toggler, :scope > span .toggler, :scope > .jstree-ocl'
        );
    const isOpen = (li) => {
        const t = directToggler(li);
        const txt = ((t && t.textContent) || '').replace(/\s+/g, '');
        if (txt.indexOf('-') !== -1) return true;
        return (
            li.classList.contains('expanded') ||
            li.classList.contains('jstree-open')
        );
    };
    const toggleDepth = (li) => {
        let d = 0;
        let p = li.parentElement;
        while (p) {
            if (p.matches && p.matches('li.toggle, li.jstree-node, li.jstree-open, li.jstree-closed')) d += 1;
            p = p.parentElement;
        }
        return d;
    };
    const collapseOtherSeasons = (keepYear) => {
        const topCandidates = Array.from(
            document.querySelectorAll(
                '#libraryView li.toggle, .tree li.toggle, .tree ul.t2 > li, #libraryView ul.t2 > li, #libraryView > ul > li'
            )
        );
        for (const li of topCandidates) {
            const raw = getLabel(li);
            if (!seasonYearRe.test(raw) && !/Season/i.test(raw)) continue;
            if ((raw || '').includes(keepYear)) continue;
            if (toggleDepth(li) > 0) continue; // only top-level seasons
            if (!isOpen(li)) continue;
            const t = directToggler(li);
            if (t) t.click();
        }
    };

    document.querySelectorAll('[data-sergio-click-target]').forEach((el) => {
        el.removeAttribute('data-sergio-click-target');
    });

    const year = seasonKey(seasonLabel);
    if (collapseOthers !== false) {
        try { collapseOtherSeasons(year); } catch (_) {}
    }

    // SAME selectors that successfully parse season children on this page.
    const candidates = Array.from(
        document.querySelectorAll(
            '#libraryView li.toggle, .tree li.toggle, .tree ul.t2 > li, #libraryView ul.t2 > li, #libraryView > ul > li'
        )
    );
    let seasonLi = null;
    let bestDepth = Infinity;
    for (const li of candidates) {
        const raw = getLabel(li);
        if (seasonKey(raw) !== year && !raw.includes(year)) continue;
        if (!seasonYearRe.test(raw) && !/Season/i.test(raw)) continue;
        const depth = toggleDepth(li);
        if (depth < bestDepth) {
            bestDepth = depth;
            seasonLi = li;
        }
    }
    if (!seasonLi) {
        return { ok: false, reason: 'season_not_found', scanned: candidates.length };
    }

    let current = seasonLi;
    for (const part of folderPath || []) {
        const target = normalize(part);
        const childUl = current.querySelector(':scope > ul');
        if (!childUl) {
            return {
                ok: false,
                reason: 'parent_not_expanded',
                at: getLabel(current),
                need: part,
            };
        }
        const kids = Array.from(childUl.querySelectorAll(':scope > li'));
        let next = null;
        // Exact match only among DIRECT children of this parent (stay in this year/folder).
        for (const kid of kids) {
            if (normalize(getLabel(kid)) === target) {
                next = kid;
                break;
            }
        }
        // Unique prefix/contains fallback â€” only if exactly one child matches.
        if (!next && target) {
            const loose = kids.filter((kid) => {
                const n = normalize(getLabel(kid));
                return n === target || n.includes(target) || target.includes(n);
            });
            if (loose.length === 1) next = loose[0];
        }
        if (!next) {
            return {
                ok: false,
                reason: 'folder_not_found',
                parent: getLabel(current),
                need: part,
                kids: kids.slice(0, 12).map((k) => getLabel(k)),
            };
        }
        current = next;
    }

    current.setAttribute('data-sergio-click-target', '1');
    const t = directToggler(current);
    const tText = ((t && t.textContent) || '').replace(/\s+/g, '');
    return {
        ok: true,
        label: getLabel(current),
        open: isOpen(current),
        hasToggler: !!t,
        togglerText: tText,
        pathLen: (folderPath || []).length,
        year,
    };
}
T©Úcollapse_othersÚfolder_pathr  c             ƒ   óÎ  K  — |xs g }| j                  t        |||dœ«      ƒ d{  –—† }|j                  d«      st        d|›d|›d|› �«       |S | j	                  d«      j
                  }|j                  «       ƒ d{  –—†  | j                  d«      ƒ d{  –—†  |j                  d	«      r,t        d
|› ddj                  |«      xs d› �«       i |¥ddi¥S t        j                  d«      ƒ d{  –—†  |j	                  d«      j
                  }|j                  «       ƒ d{  –—† dkD  r@|j                  dd¬«      ƒ d{  –—†  t        d|› ddj                  |«      xs d› �«       n?|j                  dd¬«      ƒ d{  –—†  t        d|› ddj                  |«      xs d› �«       t        j                  d«      ƒ d{  –—†  i |¥ddi¥S 7 �Œ¬7 �ŒU7 �Œ?7 Œç7 Œ¶7 Œš7 Œ\7 Œ­w)zPMark the Classic tree node and click its [+] with a real Playwright mouse event.)ÚseasonLabelÚ
folderPathÚcollapseOthersNrä   zClassic mark miss season=ú path=rÞ   z[data-sergio-click-target="1"]a×  () => {
            const li = document.querySelector('[data-sergio-click-target="1"]');
            if (!li) return;
            li.style.outline = '4px solid #0066cc';
            li.style.background = '#c5ddf3';
            li.scrollIntoView({ block: 'center' });
            setTimeout(() => {
                try {
                    li.style.outline = '';
                    li.style.background = '';
                } catch (_) {}
            }, 1800);
        }rÕ  zClassic focus (already open): õ    â†’ rK   z(year)r‘  Fç{®Gáz´?zY:scope > .toggler, :scope > span > .toggler, :scope > span .toggler, :scope > .jstree-oclr   r^  Tr‡  zClassic CLICK [+]: zClassic CLICK row: r  )rj   Ú_CLASSIC_MARK_NODE_JSr´   r^   r,   r-   rR  r�  r<  rÃ  r.   r  )r#   rË  r  r  rú  ÚmarkedrÇ  rã  s           r   rÞ  rÞ  ¾  sý  è ø€ ð Ò˜"€DØ—=‘=Üà'ØØ-ñ	
ó÷ €Fð �:‰:�dÔÜØ'¨Ð'7°v¸d¸XÀRÈÀxÐPô	
ð ˆà�\‰\Ð:Ó;×AÑA€FØ
×
+Ñ
+Ó
-×-Ð-Ø
�-‰-ð	ó÷ ð ð  ‡z�z�&ÔÜØ,¨\¨N¸%ÀÇÁÈÃÒ@ZÐRZÐ?[Ð\ô	
ð ,�&Ð+˜) UÑ+Ð+ä
�-‰-˜Ó
×Ðà�n‰nð	7ó÷ �eð ð �]‰]‹_×˜qÒ Ø�m‰m D°ˆmÓ5×5Ð5ÜÐ# L >°°s·x±xÀ³~Ò7QÈÐ6RÐSÕTà�l‰l 4¨tˆlÓ4×4Ð4ÜÐ# L >°°s·x±xÀ³~Ò7QÈÐ6RÐSÔTä
�-‰-˜Ó
×ÐØ&ˆfÐ&�i Ñ&Ð&ðiùð .ùðùð, øð øØ5øð 	5øð ús�   ‚$G%¦G§AG%Â GÂG%ÂGÂAG%Ã3GÃ42G%Ä&GÄ'G%ÅGÅ?G%ÆG!ÆA G%ÇG#ÇG%ÇG%ÇG%ÇG%ÇG%ÇG%Ç!G%Ç#G%©Úquickrƒ   r  c             ƒ   ó  K  — 	 | j                  «       ƒ d{  –—†  | j                  d|«      ƒ d{  –—†  t        j                  |rdnd«      ƒ d{  –—†  y7 Œ@7 Œ(7 Œ	# t        $ r}t        d|› �«       Y d}~yd}~ww xY w­w)zHBig red banner ON the Hudl page so the click path is visible in-browser.Nu  (msg) => {
                let el = document.getElementById('sergio-hudl-click-banner');
                if (!el) {
                    el = document.createElement('div');
                    el.id = 'sergio-hudl-click-banner';
                    el.style.cssText = [
                        'position:fixed',
                        'top:0',
                        'left:0',
                        'right:0',
                        'z-index:2147483647',
                        'background:#E31E24',
                        'color:#fff',
                        'font:bold 22px/1.35 Arial,sans-serif',
                        'padding:18px 22px',
                        'box-shadow:0 6px 20px rgba(0,0,0,.45)',
                        'pointer-events:none',
                        'letter-spacing:0.2px',
                    ].join(';');
                    document.body.appendChild(el);
                }
                el.textContent = 'SERGIO CLICK â†’ ' + msg;
                document.title = 'SERGIO | ' + msg;
            }rÂ  r×  zHudl click banner failed: )r  rj   r<  rÃ  r0   r^   )r#   rƒ   r  rm   s       r   Ú_show_hudl_click_bannerr  þ  sˆ   è ø€ ð2Ø×!Ñ!Ó#×#Ð#Ø�m‰mðð0 ó3
÷ 	
ð 	
ô6 �m‰m¡E™D¨tÓ4×4Ñ4ð9 	$øð	
øð6 	5ùÜò 2ÜÐ*¨3¨%Ð0×1Ñ1ûð2üs`   ‚B„A —A˜A ±A² A ÁAÁA ÁBÁA ÁA ÁA Á	A?Á'A:Á5BÁ:A?Á?Bc              ƒ   óª  K  — ddl m} t        | |«      ƒ d{  –—† }d|› �}d|› �}t        t        j                  d«      xs d«      }|r|t        |«      k7  r
t        «        || j                  xs dv r< || «      ƒ d{  –—† s,t        d| j                  › �«       t        |«      t        d<   |S t        «        t        | |¬	«      ƒ d{  –—†  || j                  xs dvr(t        | «      ƒ d{  –—†  t        | |«      ƒ d{  –—†  nt        | «      ƒ d{  –—†  t        | «      ƒ d{  –—†  t        |«      t        d<   |S 7 �Œ,7 ŒÆ7 Œ}7 ŒZ7 ŒH7 Œ67 Œ%­w)
zMReuse the current Manage Library page when possible to skip login/navigation.r   )rÿ  Nr  rN   r$   r&   z"Manage Library: reusing open page rƒ  )rç   rÿ  rq  rd   Ú
_ML_STICKYr´   Ú_ml_sticky_resetr   r^   r�  r   rº  r  )r#   r$   rÿ  Úresolved_team_idÚtarget_pathr'  Ú	prev_teams          r   r'  r'  $  sJ  è ø€ å8ä<¸TÀ7ÓK×KÐØ$Ð%5Ð$6Ð7€KØ'¨ }Ð5€Jä”J—N‘N 9Ó-Ò3°Ó4€IÙ�Y¤#Ð&6Ó"7Ò7ÜÔð �t—x‘x’~ 2Ñ&±]À4Ó5H×/HÐ/HÜÐ2°4·8±8°*Ð=Ô>ä #Ð$4Ó 5Œ
�9ÑØÐô Ôô ˜t¨zÔ
:×:Ð:à˜4Ÿ8™8š> rÑ*Ü Ó%×%Ð%Ü)¨$Ð0@ÓA×AÑAä Ó%×%Ð%ä
# DÓ
)×)Ð)ÜÐ 0Ó1„JˆyÑØÐð= Lùð 0Iøð ;øð 	&øØAøà%øà)ús}   ‚E˜E™A'EÂ EÂA
EÃE	Ã$EÃ0EÃ1EÄEÄEÄEÄEÄ+EÄ,EÅEÅ	EÅEÅEÅEÅEc              ƒ   ó
  ‡ ‡‡K  — |j                  «       Š‰st        d«      ‚dt        fˆˆ ˆfd„}	 t        j                   |«       d¬«      ƒ d{  –—† S 7 Œ# t        j
                  $ r}t        d‰› d�«      |‚d}~ww xY w­w)	zKStep 1: open the clicked YEAR on Hudl, then fetch only that year's folders.úseason_label is requiredr   c               “   ó   •K  — t        ‰‰	«      ƒ d {  –—† } t        ‰«      ƒ d {  –—†  t        ‰‰«      ƒ d {  –—† }|j                  d«      s!t	        |j                  d«      xs d«      dkD  rS	 t        ‰‰d¬«      ƒ d {  –—† }|r:t        d‰› dt        |«      › d�«       t        | ‰g «       d	| ‰|t        |«      d
dœS t        ‰d‰› �d
¬«      ƒ d {  –—†  	 t        ‰‰«      ƒ d {  –—†  	 t        ‰«      ƒ d {  –—†  	 t        ‰‰d¬«      ƒ d {  –—† }t        d‰› dt        |«      › d�«       t        | ‰g «       d	| ‰|t        |«      dœS 7 �ŒC7 �Œ37 �Œ"7 ŒÛ# t        $ r g }Y Œæw xY w7 Œ™7 Œ†7 Œt# t        $ r}t        d|› �«       Y d }~ŒŽd }~ww xY w7 Œƒ# t        $ r}t        d‰›d|› �«       g }Y d }~Œ¢d }~ww xY w# t        $ r+}dd l}t        d‰›d|› �«       |j                  «        ‚ d }~ww xY w­w)NrÍ  rÎ  r   r   ©Úmax_attemptszFAST PATH season already open 'z': u    folders â€” skip clickrä   T)r�   r$   rË  rû  r.   Ú	fast_pathúOpening YEAR: r  z#Season expand popup close ignored: r³  zSeason parse error for rÞ   zSeason expand error for rÖ  z' open+fetch: z folders)r�   r$   rË  rû  r.   )r'  r½  rÑ  r´   rµ   Ú_wait_and_parse_season_childrenr0   r^   r¹   Ú_ml_sticky_setr  rä  r   Ú	tracebackÚ	print_exc)
r  rE   rû  Ú	popup_excÚ	parse_excrm   r"  r�   r#   r$   s
          €€€r   Ú_runz*expand_manage_library_season.<locals>._runQ  s?  øè ø€ Ü!<¸TÀ7Ó!K×KÐÜ+¨DÓ1×1Ð1ô -¨T°5Ó9×9ˆØ�9‰9�ZÔ ¤C¨¯	©	°,Ó(?Ò(DÀ1Ó$EÈÒ$IðÜ!@Ø˜%¨aô"÷ �ñ
 ÜØ5°e°W¸CÜ˜8“}�oÐ%<ð>ôô Ð/°¸Ô;à"Ø/Ø$)Ø (Ü  ›]Ø!%ñð ô & d¨n¸U¸GÐ,DÈDÔQ×QÐQð	Ü*¨4°Ó7×7Ð7ðIÜ'¨Ó-×-Ð-ðÜ!@Ø˜%¨aô"÷ �ô 	�˜˜˜~¬c°(«m¨_¸HÐEÔFÜÐ'¨°Ô3ð Ø'Ø!Ø Ü˜“]ñ
ð 	
ðg LùØ1ùð :ùðùô ò Ø’ðúð" 	Røà7øà-ùÜò IÜÐ;¸I¸;ÐG×HÑHûðIúðùô ò ÜÐ/°¨y¸¸9¸+ÐFÔGØ•ûðûô ò 	ÛäÐ,¨U¨I°R¸°uÐ=Ô>Ø×ÑÔ!Øûð	üs  ƒG>“E”G>¦E§G>ºE»6G>Á2E! ÂEÂE! ÂAG>ÃE2ÃG>ÃG Ã-E4Ã.G Ã3E8 ÄE6ÄE8 ÄF ÄFÄF Ä:G>ÅG>ÅG>ÅE! Å!E/Å,G>Å.E/Å/G>Å4G Å6E8 Å8	FÆFÆG ÆFÆG ÆF Æ	GÆ'F?Æ:G Æ?GÇG Ç	G;Ç&G6Ç6G;Ç;G>ç     €V@rq   NzTimed out expanding season 'ú
' on Hudl.©r*   rT   r³   r<  rR   r?  )r#   r$   rË  r&  rm   r�   s   ``   @r   Úexpand_manage_library_seasonr*  I  sƒ   úè ø€ ð ×ÑÓ €EÙÜÐ3Ó4Ð4ð:
œ÷ :
ðxVÜ×%Ñ%¡d£f°dÔ;×;Ð;Ð;ùÜ×Ñò VÜÐ9¸%¸À
ÐKÓLÐRUÐUûðVüs:   …,B²A ÁAÁA ÁBÁA ÁB Á+A;Á;B Â Bc                 óT   — t        j                  dd| xs dj                  «       «      S )Nz\s*\(\d+\)\s*$r&   )r   Úsubr*   )r�   s    r   Ú_strip_count_suffixr-  “  s#   € Ü�6‰6Ð# R¨%ª+°2×)<Ñ)<Ó)>Ó?Ð?r   c                 ó"  — t        | «      }t        j                  dd|t        j                  ¬«      }t        j                  dd|t        j                  ¬«      }t        j                  dd|t        j                  ¬«      }|j	                  «       S )Nz?(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+\d{1,2}\br&   )Úflagsz<(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)(\d{1,2})\bz2(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)$)r-  r   r,  Ú
IGNORECASEr*   )r�   Úcleaneds     r   Ú_strip_date_from_labelr2  —  sx   € Ü! %Ó(€GÜ�f‰fØJØ
ØÜ�m‰mô	€Gô �f‰fØGØ
ØÜ�m‰mô	€Gô �f‰fØ=Ø
ØÜ�m‰mô	€Gð �=‰=‹?Ðr   c                 óV   — | D �cg c]  }t        |«      sŒt        |«      ‘Œ c}S c c}w rÉ   )r2  )r  Úps     r   Ú_normalize_folder_pathr5  ®  s)   € Ù/:ÓX©{¨!Ô>TÐUVÕ>WÔ" 1Õ%¨{ÑXÐXùÒXs   …&–&udP  
({ seasonLabel, folderPath, action, clipLabel, desiredSelected }) => {
    const monthRe = new RegExp('(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+(\d{1,2})(?!\d)', 'i');
    const gluedMonthRe = new RegExp('(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)(\d{1,2})(?!\d)', 'i');
    const wordGluedMonthRe = new RegExp('[A-Za-z](Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+(\d{1,2})(?!\d)', 'i');
    const trailingMonthRe = /(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)$/i;
    const hrRe = /([\d.]+)\s*hr\b/i;
    const seasonYearRe = /(\d{4}-\d{4})/;
    const stripCount = (s) => (s || '').replace(/\s*\(\d+\)\s*$/, '').trim();

    const normalizeForMatch = (s) => {
        let v = stripCount((s || '').trim());
        v = v.replace(/\s+Season$/i, '').trim();
        v = v.replace(hrRe, '').trim();
        v = v.replace(monthRe, '').replace(gluedMonthRe, '').replace(trailingMonthRe, '').trim();
        return v.replace(/\s{2,}/g, ' ').trim();
    };

    const seasonKey = (s) => {
        const m = normalizeForMatch(s).match(seasonYearRe);
        return m ? m[1] : normalizeForMatch(s);
    };

    const root =
        document.querySelector('#libraryView') ||
        document.querySelector('.manage-library-right-column') ||
        document.querySelector('.tree') ||
        document.body;

    const getLiLabel = (li) => {
        const anchor = li.querySelector(':scope > a.jstree-anchor') || li.querySelector(':scope > a');
        if (anchor) return (anchor.textContent || '').trim();
        const link = li.querySelector(':scope > span > a, :scope > a');
        if (link) return (link.textContent || '').trim();
        const clone = li.cloneNode(true);
        clone.querySelector('.toggler')?.remove();
        clone.querySelector('ul')?.remove();
        clone.querySelector('.jstree-ocl')?.remove();
        return (clone.textContent || '').replace(/\s+/g, ' ').trim();
    };

    const isFolderLi = (li) => {
        if (li.classList.contains('toggle')) return true;
        if (li.classList.contains('jstree-closed') || li.classList.contains('jstree-open')) return true;
        if (li.querySelector(':scope > .toggler, :scope > span .toggler')) return true;
        if (li.querySelector(':scope > .jstree-ocl')) return true;
        return false;
    };

    const isLiLeaf = (li) => {
        if (isFolderLi(li)) return false;
        if (li.classList.contains('jstree-leaf')) return true;
        if (li.querySelector('.jstree-checkbox, input[type="checkbox"]')) return true;
        return !li.querySelector(':scope > ul');
    };

    const directToggler = (li) =>
        li.querySelector(
            ':scope > .toggler, :scope > span > .toggler, :scope > span .toggler, :scope > .jstree-ocl'
        );

    const expandLi = (li) => {
        // CRITICAL: only the ROW's own [+]/ never a nested child's toggler.
        // A descendant ".toggler" match was scrolling/clicking the wrong folder.
        try { li.scrollIntoView({ block: 'center', inline: 'nearest' }); } catch (_) {}
        const prev = li.getAttribute('style') || '';
        li.setAttribute('style', prev + ';outline:3px solid #0066cc!important;background:#c5ddf3!important;');
        setTimeout(() => { try { li.setAttribute('style', prev); } catch (_) {} }, 2800);

        const toggler = directToggler(li);
        const ocl = li.querySelector(':scope > .jstree-ocl');
        const togglerText = ((toggler && toggler.textContent) || '').replace(/\s+/g, '').trim();
        const showsPlus = togglerText === '+' || togglerText.indexOf('+') !== -1;
        const showsMinus = togglerText === '-' || togglerText.indexOf('-') !== -1;
        const isOpen =
            li.classList.contains('jstree-open') ||
            (li.classList.contains('toggle') && li.classList.contains('expanded')) ||
            showsMinus;
        const isCollapsed =
            !isOpen && (
                li.classList.contains('jstree-closed') ||
                li.classList.contains('collapsed') ||
                showsPlus ||
                (li.classList.contains('toggle') && !li.classList.contains('expanded'))
            );

        // Hudl Classic [+] â€” click ONLY this row's toggler so the website opens.
        if (isCollapsed || (!isOpen && toggler)) {
            if (toggler) { toggler.click(); return true; }
            if (ocl) { ocl.click(); return true; }
            const anchor = li.querySelector(':scope > a.jstree-anchor, :scope > a');
            if (anchor) { anchor.click(); return true; }
            li.click();
            return true;
        }

        // Already open ([-]) â€” flash + scroll only. Do NOT click (would collapse).
        return true;
    };

    const collapseOtherSeasons = (keepYear) => {
        const lists = [
            ...document.querySelectorAll('.tree ul.t2'),
            ...document.querySelectorAll('#libraryView > ul'),
            ...document.querySelectorAll('#libraryView ul.t2'),
        ];
        const seen = new Set();
        for (const ul of lists) {
            for (const li of Array.from(ul.children || [])) {
                if (!(li instanceof HTMLElement) || li.tagName !== 'LI' || seen.has(li)) continue;
                seen.add(li);
                const raw = getLiLabel(li);
                if (raw.includes(keepYear)) continue;
                const open =
                    li.classList.contains('expanded') || li.classList.contains('jstree-open');
                if (!open) continue;
                const t = directToggler(li);
                if (t) t.click();
            }
        }
    };

    const parseMeta = (rawLabel, rowText) => {
        let label = (rawLabel || '').trim();
        let count = null;
        const countMatch = label.match(/\((\d+)\)\s*$/);
        if (countMatch) {
            count = parseInt(countMatch[1], 10);
            label = label.replace(/\(\d+\)\s*$/, '').trim();
        }
        label = label.replace(monthRe, '').replace(gluedMonthRe, '').replace(/([A-Za-z])(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)/gi, '$1').replace(trailingMonthRe, '').replace(hrRe, '').replace(/\s{2,}/g, ' ').trim();
        const text = (rowText || rawLabel || label).split(hrRe)[0];
        const fullDate = text.match(monthRe);
        const gluedDate = text.match(gluedMonthRe);
        const wordGluedDate = text.match(wordGluedMonthRe);
        const trailingDate = text.match(trailingMonthRe);
        const dateMatch = fullDate || gluedDate || wordGluedDate || trailingDate;
        const hrMatch = (rowText || rawLabel || '').match(hrRe);
        return {
            label: stripCount(label),
            date: fullDate ? `${fullDate[1]} ${fullDate[2]}` : gluedDate ? `${gluedDate[1]} ${gluedDate[2]}` : wordGluedDate ? `${wordGluedDate[1]} ${wordGluedDate[2]}` : trailingDate ? trailingDate[0] : undefined,
            hours: hrMatch ? parseFloat(hrMatch[1]) : undefined,
            count,
        };
    };

    const labelsMatch = (a, b) => {
        const na = normalizeForMatch(a);
        const nb = normalizeForMatch(b);
        if (!na || !nb) return false;
        if (na === nb) return true;
        const ya = seasonKey(a);
        const yb = seasonKey(b);
        if (ya && yb && ya === yb) return true;
        if (na.includes(nb) || nb.includes(na)) return true;
        // Whitespace-insensitive fallback: handles stray spaces inside numeric
        // dates like "5/ 20 Tros IR" vs "5/20 Tros IR".
        const sa = na.replace(/\s+/g, '').toLowerCase();
        const sb = nb.replace(/\s+/g, '').toLowerCase();
        if (!sa || !sb) return false;
        return sa === sb || sa.includes(sb) || sb.includes(sa);
    };

    const findSeasonLi = (season) => {
        const seasonTarget = seasonKey(season);
        // Same discovery as the working season children parser.
        const candidates = Array.from(
            document.querySelectorAll(
                '#libraryView li.toggle, .tree li.toggle, .tree ul.t2 > li, #libraryView ul.t2 > li, #libraryView > ul > li'
            )
        );
        let best = null;
        let bestDepth = Infinity;
        for (const li of candidates) {
            const raw = getLiLabel(li);
            if (seasonKey(raw) !== seasonTarget && !labelsMatch(raw, season)) continue;
            let depth = 0;
            let p = li.parentElement;
            while (p) {
                if (p.matches && p.matches('li.toggle, li.jstree-node, li.jstree-open, li.jstree-closed')) depth += 1;
                p = p.parentElement;
            }
            if (depth < bestDepth) {
                bestDepth = depth;
                best = li;
            }
        }
        return best;
    };

    const findNodeLi = (season, path) => {
        let current = findSeasonLi(season);
        if (!current) return null;

        for (const folder of path || []) {
            const target = normalizeForMatch(folder);
            const childUl = current.querySelector(':scope > ul');
            if (!childUl) return null;
            const kids = Array.from(childUl.querySelectorAll(':scope > li'));
            let next = null;
            // Exact label match first â€” stay inside this parent folder only.
            for (const childLi of kids) {
                if (normalizeForMatch(getLiLabel(childLi)) === target) {
                    next = childLi;
                    break;
                }
            }
            if (!next) {
                for (const childLi of kids) {
                    const raw = getLiLabel(childLi);
                    if (labelsMatch(raw, folder) || labelsMatch(raw, target)) {
                        next = childLi;
                        break;
                    }
                }
            }
            if (!next) return null;
            current = next;
        }
        return current;
    };

    // Hudl Classic stores hover tooltip fields in title / data-original-title
    // (Name, Date, Created by, Shared with, # of Clips, Total Length, â€¦).
    const extractItemMeta = (li, fallbackLabel, fallbackDate) => {
        const anchor =
            li.querySelector(':scope > a.jstree-anchor') ||
            li.querySelector(':scope > a') ||
            li.querySelector(':scope > span > a');
        const el = anchor || li;
        let raw =
            el.getAttribute('data-original-title') ||
            el.getAttribute('title') ||
            li.getAttribute('data-original-title') ||
            li.getAttribute('title') ||
            '';
        raw = String(raw)
            .replace(/<br\s*\/?>/gi, '\n')
            .replace(/<\/?(?:b|strong|span|div|p|i)[^>]*>/gi, '')
            .replace(/&nbsp;/gi, ' ')
            .replace(/&amp;/gi, '&')
            .replace(/&#10;/g, '\n')
            .replace(/&#13;/g, '')
            .replace(/&lt;/gi, '<')
            .replace(/&gt;/gi, '>');

        const item = {};
        if (fallbackLabel) item.name = fallbackLabel;
        if (fallbackDate) item.date = fallbackDate;

        const apply = (key, value) => {
            if (value == null) return;
            const v = String(value).replace(/\s+/g, ' ').trim();
            if (!v) return;
            const k = String(key || '').toLowerCase().trim();
            if (k === 'name') item.name = v;
            else if (k === 'date' || k === 'created on') item.date = item.date || v;
            else if (k === 'created by') item.created_by = v;
            else if (k === 'shared with') {
                item.shared_with = v.split(/,|\n/).map((s) => s.trim()).filter(Boolean);
            } else if (k === '# of clips' || k === 'number of clips' || k === 'clips') {
                const n = parseInt(v, 10);
                if (!Number.isNaN(n)) item.clips = n;
            } else if (k === 'total length') item.total_length = v;
            else if (k === 'duration') item.duration = v;
            else if (k === 'angles') item.angles = v;
            else if (k === 'type') item.type = v;
            else if (k === 'tags') item.tags = v;
        };

        for (const line of raw.split(/\r?\n|;/)) {
            const m = line.match(/^\s*([A-Za-z#][A-Za-z #/]*?)\s*:\s*(.+)\s*$/);
            if (m) apply(m[1], m[2]);
        }

        for (const [attr, key] of [
            ['data-created-by', 'created by'],
            ['data-shared-with', 'shared with'],
            ['data-date', 'date'],
            ['data-clips', '# of clips'],
            ['data-total-length', 'total length'],
            ['data-angles', 'angles'],
        ]) {
            apply(key, el.getAttribute(attr) || li.getAttribute(attr));
        }

        try {
            if (window.jQuery) {
                const data = window.jQuery(li).data() || {};
                const od = data.jstree && data.jstree.original ? data.jstree.original : data.original || data;
                apply('created by', od.createdBy || od.created_by || od.CreatedBy);
                const shared = od.sharedWith || od.shared_with || od.SharedWith;
                apply(
                    'shared with',
                    Array.isArray(shared) ? shared.join(', ') : shared
                );
                apply('date', od.date || od.Date);
                apply('# of clips', od.clips || od.clipCount || od.ClipCount);
                apply('total length', od.totalLength || od.total_length);
                apply('angles', od.angles || od.Angles);
                apply('type', od.type || od.Type);
                apply('tags', od.tags || od.Tags);
            }
        } catch (_) {}

        if (!item.name && fallbackLabel) item.name = fallbackLabel;
        return item;
    };

    const parseDirectChildren = (nodeLi) => {
        const childUl = nodeLi.querySelector(':scope > ul');
        if (!childUl) return [];
        return Array.from(childUl.querySelectorAll(':scope > li')).map((childLi, idx) => {
            const rowText = (childLi.innerText || '').replace(/\s+/g, ' ').trim();
            const raw = getLiLabel(childLi);
            const meta = parseMeta(raw, rowText);
            const itemMeta = extractItemMeta(childLi, meta.label, meta.date);
            const nodeId =
                childLi.id ||
                childLi.getAttribute('data-cat-id') ||
                childLi.getAttribute('data-categoryid') ||
                childLi.getAttribute('data-linkid') ||
                `${meta.label}-${idx}`;
            if (isLiLeaf(childLi)) {
                return {
                    id: String(nodeId),
                    label: meta.label,
                    date: meta.date || itemMeta.date,
                    selectable: true,
                    meta: itemMeta,
                    children: [],
                };
            }
            return {
                id: String(nodeId),
                label: meta.label,
                date: meta.date || itemMeta.date,
                hours: meta.hours,
                count: meta.count,
                meta: itemMeta,
                children: [],
            };
        });
    };

    // Expand opens the target node. Parents are opened by prior Python steps
    // (with waits) so Hudl Classic can lazy-load children between clicks.
    if (action === 'expand') {
        const year = seasonKey(seasonLabel);
        try { collapseOtherSeasons(year); } catch (_) {}

        const seasonLi = findSeasonLi(seasonLabel);
        if (!seasonLi) {
            return { ok: false, reason: 'season_not_found', expanded: false, children: [] };
        }
        expandLi(seasonLi);

        if (!folderPath || folderPath.length === 0) {
            try { seasonLi.scrollIntoView({ block: 'center', inline: 'nearest' }); } catch (_) {}
            return { ok: true, expanded: true, children: [] };
        }

        const targetLi = findNodeLi(seasonLabel, folderPath);
        if (!targetLi) {
            return { ok: false, reason: 'node_not_found', expanded: false, children: [] };
        }
        expandLi(targetLi);
        try { targetLi.scrollIntoView({ block: 'center', inline: 'nearest' }); } catch (_) {}
        return { ok: true, expanded: true, alreadyOpen: false, children: [] };
    }

    const li = findNodeLi(seasonLabel, folderPath || []);
    if (!li) {
        return { ok: false, reason: 'node_not_found', children: [] };
    }

    if (action === 'select_clip') {
        const childUl = li.querySelector(':scope > ul');
        if (!childUl) return { ok: false, reason: 'no_children', selected: false };
        const clipTarget = stripCount(clipLabel || '');
        const children = Array.from(childUl.querySelectorAll(':scope > li'));

        const clickChild = (childLi, method) => {
            try { childLi.scrollIntoView({ block: 'center', inline: 'nearest' }); } catch (_) {}
            const prev = childLi.getAttribute('style') || '';
            childLi.setAttribute('style', prev + ';outline:3px solid #0066cc!important;background:#c5ddf3!important;');
            setTimeout(() => { try { childLi.setAttribute('style', prev); } catch (_) {} }, 1200);
            const cb = childLi.querySelector('.jstree-checkbox, input[type="checkbox"]');
            if (cb) {
                cb.click();
                return { ok: true, selected: true, method, children: [] };
            }
            const anchor = childLi.querySelector('.jstree-anchor, :scope > a');
            if (anchor) {
                anchor.click();
                return { ok: true, selected: true, method, children: [] };
            }
            childLi.click();
            return { ok: true, selected: true, method, children: [] };
        };

        // Pass 1: exact normalized match. Prevents "Off the Ball" from matching
        // when we actually want the sibling "Off the Ball 2" (and vice versa).
        const targetNorm = normalizeForMatch(clipTarget) || normalizeForMatch(clipLabel || '');
        if (targetNorm) {
            for (const childLi of children) {
                if (normalizeForMatch(getLiLabel(childLi)) === targetNorm) {
                    return clickChild(childLi, 'exact');
                }
            }
        }

        // Pass 2: loose fallback (substring / season-key match) for odd labels.
        for (const childLi of children) {
            const raw = getLiLabel(childLi);
            if (!labelsMatch(raw, clipTarget) && !labelsMatch(raw, clipLabel || '')) continue;
            return clickChild(childLi, 'loose');
        }
        return { ok: false, reason: 'clip_not_found', selected: false, children: [] };
    }

    if (action === 'set_clip') {
        const childUl = li.querySelector(':scope > ul');
        if (!childUl) return { ok: false, reason: 'no_children' };
        const children = Array.from(childUl.querySelectorAll(':scope > li'));
        const clipTarget = stripCount(clipLabel || '');
        const targetNorm = normalizeForMatch(clipTarget) || normalizeForMatch(clipLabel || '');

        let match = null;
        if (targetNorm) {
            for (const childLi of children) {
                if (normalizeForMatch(getLiLabel(childLi)) === targetNorm) {
                    match = childLi;
                    break;
                }
            }
        }
        if (!match) {
            for (const childLi of children) {
                const raw = getLiLabel(childLi);
                if (labelsMatch(raw, clipTarget) || labelsMatch(raw, clipLabel || '')) {
                    match = childLi;
                    break;
                }
            }
        }
        if (!match) return { ok: false, reason: 'clip_not_found' };

        const cbInput = match.querySelector('input[type="checkbox"]');
        const anchor = match.querySelector('.jstree-anchor');
        const isChecked = () => {
            if (match.classList.contains('jstree-checked')) return true;
            if (cbInput) return !!cbInput.checked;
            if (anchor && anchor.classList.contains('jstree-clicked')) return true;
            if (match.getAttribute('aria-selected') === 'true') return true;
            return false;
        };

        const current = isChecked();
        if (current === !!desiredSelected) {
            return { ok: true, changed: false, checked: current };
        }
        const clickable =
            match.querySelector('.jstree-checkbox, ins.jstree-checkbox, input[type="checkbox"]') ||
            anchor ||
            match;
        clickable.click();
        return { ok: true, changed: true, checked: !!desiredSelected };
    }

    return { ok: true, children: parseDirectChildren(li) };
}
r�  Ú
clip_labelr5   c              ƒ   óÜ   K  — 	 ||||xs dt        |«      dœ}| j                  t        |«      ƒ d {  –—† S 7 Œ# t        $ r'}t	        d|› �«       dt        |«      g dœcY d }~S d }~ww xY w­w)Nr&   )r  r  r�  Ú	clipLabelÚdesiredSelectedzjstree scoped action failed: F)rä   rÛ  rû  )r·   rj   Ú_JSTREE_SCOPED_JSr0   r^   rd   )r#   rË  r  r�  r6  r5   Úpayloadrm   s           r   Ú_jstree_scoped_actionr<  �  sw   è ø€ ðAà'Ø%ØØ#Ò) rÜ# H›~ñ
ˆð —]‘]Ô#4°gÓ>×>Ð>Ð>ùÜò AÜÐ-¨c¨UÐ3Ô4Ø¤s¨3£x¸RÑ@Õ@ûðAüs=   ‚A,„.9 ²7³9 ¶A,·9 ¹	A)ÁA$ÁA)ÁA,Á$A)Á)A,c              ƒ   óâ   K  — t        | ||d«      ƒ d {  –—† }|j                  d«      r@|j                  d«      r/|j                  d«      st        j                  d«      ƒ d {  –—†  yy7 ŒV7 Œ­w)NÚexpandrä   rÍ  rÝ  rÜ  TF)r<  r´   r<  rÃ  ©r#   rË  r  r  s       r   Ú_expand_jstree_node_scopedr@  £  sb   è ø€ ô )¨¨|¸[È(ÓS×S€FØ‡z�z�$Ô˜FŸJ™J zÔ2à�z‰z˜-Ô(Ü—-‘- Ó$×$Ð$ØØð Tøð %ús"   ‚A/”A+•AA/Á$A-Á%A/Á-A/c           
   ƒ   óÐ   K  — t        | ||d«      ƒ d {  –—† }|j                  d«      s%t        d|›d|›d|j                  d«      › �«       g S |j                  d«      xs g S 7 ŒO­w)NÚparserä   zjstree parse miss: season=r
  ú reason=rÛ  rû  )r<  r´   r^   r?  s       r   Ú_parse_jstree_scoped_childrenrD  ¯  sq   è ø€ ô )¨¨|¸[È'ÓR×R€FØ�:‰:�dÔÜÐ*¨<Ð*:¸&ÀÀÈxÐX^×XbÑXbÐckÓXlÐWmÐnÔoØˆ	Ø�:‰:�jÓ!Ò' RÐ'ð	 Sús   ‚A&”A$•AA&a   
async ({ seasonLabel, folderPath, clipLabel }) => {
    const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
    const seasonYearRe = /(\d{4}-\d{4})/;
    const monthRe = /(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+\d{1,2}\b/i;
    const stripCount = (s) => (s || '').replace(/\s*\(\d+\)\s*$/, '').trim();
    const normalize = (s) =>
        stripCount(s)
            .replace(/\s+Season$/i, '')
            .replace(monthRe, '')
            .replace(/[\d.]+\s*hr\b/i, '')
            .replace(/\s{2,}/g, ' ')
            .trim();
    const seasonKey = (s) => {
        const m = normalize(s).match(seasonYearRe);
        return m ? m[1] : normalize(s);
    };
    const labelsMatch = (a, b) => {
        const na = normalize(a);
        const nb = normalize(b);
        if (!na || !nb) return false;
        if (na === nb) return true;
        const ya = seasonKey(a);
        const yb = seasonKey(b);
        if (ya && yb && ya === yb) return true;
        const sa = na.replace(/\s+/g, '').toLowerCase();
        const sb = nb.replace(/\s+/g, '').toLowerCase();
        return sa === sb || sa.includes(sb) || sb.includes(sa);
    };
    const getLiLabel = (li) => {
        const anchor = li.querySelector(':scope > a.jstree-anchor') || li.querySelector(':scope > a');
        if (anchor) return (anchor.textContent || '').trim();
        const clone = li.cloneNode(true);
        clone.querySelector('.toggler')?.remove();
        clone.querySelector('ul')?.remove();
        return (clone.textContent || '').replace(/\s+/g, ' ').trim();
    };
    const isFolderLi = (li) => {
        if (li.classList.contains('toggle')) return true;
        if (li.classList.contains('jstree-closed') || li.classList.contains('jstree-open')) return true;
        if (li.querySelector(':scope > .toggler, :scope > span .toggler')) return true;
        if (li.querySelector(':scope > .jstree-ocl')) return true;
        return false;
    };
    const isLiLeaf = (li) => {
        if (isFolderLi(li)) return false;
        if (li.classList.contains('jstree-leaf')) return true;
        if (li.querySelector('.jstree-checkbox, input[type="checkbox"]')) return true;
        return !li.querySelector(':scope > ul');
    };

    const tipSelectors = [
        '#tiptip_content',
        '#tiptip_holder',
        '.tiptip_content',
        '.ui-tooltip-content',
        '.ui-tooltip',
        '.tooltip-inner',
        '.qtip-content',
        '[class*="tooltip"]',
    ];
    const readLiveTip = () => {
        for (const sel of tipSelectors) {
            for (const el of document.querySelectorAll(sel)) {
                const style = window.getComputedStyle(el);
                if (style && (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0')) {
                    continue;
                }
                const t = (el.innerText || el.textContent || '').trim();
                if (t.length > 8) return t;
            }
        }
        return '';
    };

    const parseTip = (raw, fallbackLabel) => {
        const text = String(raw || '')
            .replace(/<br\s*\/?>/gi, '\n')
            .replace(/<[^>]+>/g, '');
        const item = { name: fallbackLabel };
        for (const line of text.split(/\r?\n|;/)) {
            const m = line.match(/^\s*([A-Za-z#][A-Za-z #/]*?)\s*:\s*(.+)\s*$/);
            if (!m) continue;
            const k = m[1].toLowerCase().trim();
            const v = m[2].replace(/\s+/g, ' ').trim();
            if (!v) continue;
            if (k === 'name') item.name = v;
            else if (k === 'date' || k === 'created on') item.date = v;
            else if (k === 'created by') item.created_by = v;
            else if (k === 'shared with') {
                item.shared_with = v.split(/,/).map((s) => s.trim()).filter(Boolean);
            } else if (k === '# of clips' || k === 'number of clips' || k === 'clips') {
                const n = parseInt(v, 10);
                if (!Number.isNaN(n)) item.clips = n;
            } else if (k === 'total length') item.total_length = v;
            else if (k === 'duration') item.duration = v;
            else if (k === 'angles') item.angles = v;
            else if (k === 'type') item.type = v;
            else if (k === 'tags') item.tags = v;
        }
        return item;
    };

    const readTitleMeta = (li, fallbackLabel) => {
        const anchor =
            li.querySelector(':scope > a.jstree-anchor') ||
            li.querySelector(':scope > a') ||
            li;
        let raw =
            anchor.getAttribute('data-original-title') ||
            anchor.getAttribute('title') ||
            li.getAttribute('title') ||
            '';
        raw = String(raw)
            .replace(/<br\s*\/?>/gi, '\n')
            .replace(/&#10;/g, '\n')
            .replace(/<[^>]+>/g, '');
        return parseTip(raw, fallbackLabel);
    };

    const findSeasonLi = () => {
        const target = seasonKey(seasonLabel);
        const candidates = Array.from(
            document.querySelectorAll(
                '#libraryView li.toggle, .tree li.toggle, .tree ul.t2 > li, #libraryView ul.t2 > li, #libraryView > ul > li'
            )
        );
        let best = null;
        let bestDepth = Infinity;
        for (const li of candidates) {
            const raw = getLiLabel(li);
            if (seasonKey(raw) !== target && !labelsMatch(raw, seasonLabel)) continue;
            let depth = 0;
            let p = li.parentElement;
            while (p) {
                if (p.matches && p.matches('li.toggle, li.jstree-node, li.jstree-open, li.jstree-closed')) depth += 1;
                p = p.parentElement;
            }
            if (depth < bestDepth) {
                bestDepth = depth;
                best = li;
            }
        }
        return best;
    };

    let current = findSeasonLi();
    if (!current) return { ok: false, reason: 'season_not_found' };
    for (const folder of folderPath || []) {
        const childUl = current.querySelector(':scope > ul');
        if (!childUl) return { ok: false, reason: 'path_not_found' };
        const kids = Array.from(childUl.querySelectorAll(':scope > li'));
        let next = null;
        for (const childLi of kids) {
            if (labelsMatch(getLiLabel(childLi), folder)) {
                next = childLi;
                break;
            }
        }
        if (!next) return { ok: false, reason: 'folder_not_found', folder };
        current = next;
    }

    const childUl = current.querySelector(':scope > ul');
    if (!childUl) return { ok: false, reason: 'no_children' };
    const kids = Array.from(childUl.querySelectorAll(':scope > li')).filter(isLiLeaf);
    const target = stripCount(clipLabel || '');
    let match = kids.find((li) => normalize(getLiLabel(li)) === normalize(target));
    if (!match) {
        match = kids.find((li) => labelsMatch(getLiLabel(li), target));
    }
    if (!match) return { ok: false, reason: 'clip_not_found' };

    const label = stripCount(getLiLabel(match));
    let meta = readTitleMeta(match, label);
    if (meta.created_by || meta.shared_with) {
        return { ok: true, label, meta, source: 'title' };
    }

    const anchor =
        match.querySelector(':scope > a.jstree-anchor') ||
        match.querySelector(':scope > a') ||
        match;
    try {
        anchor.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true, cancelable: true, view: window }));
        anchor.dispatchEvent(new MouseEvent('mouseover', { bubbles: true, cancelable: true, view: window }));
    } catch (_) {}
    await sleep(180);
    const tip = readLiveTip();
    try {
        anchor.dispatchEvent(new MouseEvent('mouseout', { bubbles: true, cancelable: true, view: window }));
        anchor.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true, cancelable: true, view: window }));
    } catch (_) {}

    if (tip) {
        const live = parseTip(tip, label);
        meta = { ...meta, ...Object.fromEntries(Object.entries(live).filter(([, v]) => v != null && v !== '')) };
        return { ok: true, label, meta, source: 'hover' };
    }
    return { ok: true, label, meta, source: 'title_partial' };
}
c           
   ƒ   ó€  K  — |xs dj                  «       }t        t        |xs dj                  «       «      «      }t        |xs g D �cg c]#  }|j                  «       sŒ|j                  «       ‘Œ% c}«      }|r|st	        d«      ‚t        | |«      ƒ d{  –—† }	t        | «      ƒ d{  –—†  	 t        | ||«      ƒ d{  –—†  	 | j                  t        |||dœ«      ƒ d{  –—† }t        |t        «      s	d|	d	d|idœS |j                  d
«      rd
nd|	|||j                  d«      xs ||j                  d«      xs d|i|j                  d«      |j                  d«      dœS c c}w 7 ŒÒ7 ŒÁ7 Œ­# t        $ r}
t        d|
› �«       Y d}
~
ŒÇd}
~
ww xY w7 Œ°# t        $ r}
d|	t        |
«      d|idœcY d}
~
S d}
~
ww xY w­w)uK   Read Hudl hover tooltip fields (Created by, Shared with, â€¦) for one clip.r&   z#season_label and label are requiredNzclip meta navigate soft-fail: )r  r  r8  r€   r‡   )r�   r$   rÛ  rl   Úinvalid_resulträ   r�   rl   ÚsourcerÛ  )r�   r$   rË  r  r�   rl   rG  rÛ  )r*   r2  r-  r5  rT   r'  r½  Ú_navigate_manage_library_pathr0   r^   rj   Ú_CLIP_HOVER_META_JSrd   r±   r³   r´   )r#   r$   rË  r  r�   ÚseasonÚclipr4  rú  r  rm   r  s               r   Úget_manage_library_clip_metarL  …  sÐ  è ø€ ð Ò ˜b×'Ñ'Ó)€FÜ!Ô"5°u²{À×6IÑ6IÓ6KÓ"LÓM€DÜ!°{Ò7HÀbÐ7HÓ"WÑ7H°ÈQÏWÉWÍY 1§7¡7¥9Ð7HÑ"WÓX€DÙ™ÜÐ>Ó?Ð?ä8¸¸wÓG×GÐÜ
'¨Ó
-×-Ð-ð6Ü+¨D°&¸$Ó?×?Ð?ð
Ø—}‘}Üà%Ø"Ø!ñó
÷ 
ˆô  �fœdÔ#àØ'Ø&Ø˜T�Nñ	
ð 	
ð !Ÿ*™* TÔ*‘$°Ø#ØØØ—‘˜GÓ$Ò,¨Ø—
‘
˜6Ó"Ò4 v¨t nØ—*‘*˜XÓ&Ø—*‘*˜XÓ&ñ	ð 	ùòM #Xð HøØ-øð 	@ùÜò 6ÜÐ.¨s¨eÐ4×5Ñ5ûð6úð
ùô ò 
àØ'Ü˜#“hØ˜T�Nñ	
õ 	
ûð
üs¯   ‚AF>Á
E%Á E%Á2%F>ÂE*ÂF>Â*E,Â+F>Â0E0 Ã E.ÃE0 ÃF Ã#FÃ$F Ã(BF>Å,F>Å.E0 Å0	FÅ9FÆF>ÆFÆF>ÆF Æ	F;ÆF6Æ0F;Æ1F>Æ6F;Æ;F>r  c              ƒ   ó0  K  — dt         dt        t        t        f   fd„}g }d}t        |«      D ]²  }|dz   }g }t	        | |g «      ƒ d{  –—† }|r|j                  |«       t        | |«      ƒ d{  –—† }	|	r|j                  |	«       |D ]  }
 ||
«       ||«      kD  sŒ|
}Œ t        |«      dk\  r n1|r|dk\  r n(||dz
  k  sŒ–t        j                  d«      ƒ d{  –—†  Œ´ |r(t        d	|› d
t        |«      › d|› d|dk7  rdnd› d�	«       |S 7 ŒÆ7 Œ¡7 Œ6­w)zÎWait briefly for Hudl lazy-load after season expand, then parse children.

    Returns as soon as a solid child list appears so year switches stay fast
    when the Manage Library page is already open.
    rû  r   c                 óF   — | syt        d„ | D «       «      }t        | «      |fS )N)r   r   c              3   ó†   K  — | ]9  }|j                  d «      s&|j                  d«      €|j                  d«      �d–— Œ; y­w)Ú
selectableÚhoursNr.   r   rã   )rË   Úcs     r   rÎ   zB_wait_and_parse_season_children.<locals>._score.<locals>.<genexpr>Í  s?   è ø€ ð 
á�Ø—5‘5˜Ô&Ø—‘�w“Ð+¨q¯u©u°W«~Ð/Iô Ùùs   ‚?A)rð   r¹   )rû  Úfolderss     r   Ú_scorez/_wait_and_parse_season_children.<locals>._scoreÊ  s0   € ÙØÜñ 
áó
ó 
ˆô �H“˜wÐ'Ð'r   r   r   NrN  ç¸…ëQ¸¾?rÖ  z	' parsed z children (after z attemptr´  r&   r.  )r²   Útuplerµ   r…  rD  r¸   rô  r¹   r<  rÃ  r^   )r#   rË  r  rT  ÚbestÚused_attemptsr“  r²  ÚscopedÚpage_childrenrû  s              r   r   r   Á  sA  è ø€ ð	(œð 	(¤%¬¬S¨¡/ó 	(ð €DØ€MÜ˜Ö&ˆØ !™ˆØ!#ˆ
Ü4°T¸<ÈÓL×LˆÙØ×Ñ˜fÔ%Ü>¸tÀ\ÓR×RˆÙØ×Ñ˜mÔ,ã"ˆHÙ�hÓ¡&¨£,Ó.Ø‘ð #ô
 ˆt‹9˜Š>ÙÙ�G˜q’LÙØ�\ AÑ%Ó%Ü—-‘- Ó%×%Ñ%ð) 'ñ, ÜØ�|�n I¬c°$«i¨[ð 9Ø#�_ H°MÀQÒ4F©SÈBÐ,OÈqðRô	
ð €Kð1 Møð Søð &úsB   ‚ADÁ	DÁ
&DÁ0DÁ1-DÂ%DÃDÃDÃ3DÄDÄDc              ƒ   ó¸   K  — t        |«      D ]C  }t        | ||«      ƒ d{  –—† }|r|c S ||dz
  k  sŒ't        j                  d«      ƒ d{  –—†  ŒE g S 7 Œ47 Œ
­w)z:Parse folder children only under the selected season path.Nr   rU  )r…  rD  r<  rÃ  )r#   rË  r  r  r“  rû  s         r   Ú_wait_and_parse_folder_childrenr\  õ  sb   è ø€ ô ˜Ö&ˆÜ6°t¸\È;ÓW×WˆÙØŠOØ�\ AÑ%Ó%Ü—-‘- Ó%×%Ñ%ð 'ð €Ið Xøð &ús&   ‚A¡A¢AµAÁAÁ	AÁAÚprefixÚfolderc              ƒ   ó   K  — ||gz   }	 t        | ||t        |«      dk(  ¬«      ƒ d{  –—† }|j                  d«      r!t        d|›ddj	                  |«      › �«       y|j                  d	«      d
k(  r}t        | ||d¬«      ƒ d{  –—†  t        j                  d«      ƒ d{  –—†  t        | ||d¬«      ƒ d{  –—† }|j                  d«      r!t        d|›ddj	                  |«      › �«       yt        | ||«      ƒ d{  –—† r!t        d|›ddj	                  |«      › �«       yt        |«      }t        j                  d|«      }|r|j                  d«      n|j                  dd«      j                  «       }		 | j                  d|	› d�«      j                   }
|
j#                  «       ƒ d{  –—† dk(  r"| j                  d|	› d|	› d�«      j                   }
|
j#                  «       ƒ d{  –—† dk(  rt        d|›d|›d�«       y|
}|D ]œ  }t        |«      }|j                  d|› d|› d�«      j                   }|j#                  «       ƒ d{  –—† dk(  r"|j                  d|› d|› d�«      j                   }|j#                  «       ƒ d{  –—† dk(  rt        d|›d|›�«        y|}Œž |j                  d|› d|› d�«      j                   }|j#                  «       ƒ d{  –—† dk(  r"|j                  d|› d|› d�«      j                   }|j#                  «       ƒ d{  –—† dk(  rt        d|›d |›d!�«       y|j%                  «       ƒ d{  –—†  |j                  d"«      j                   }|j#                  «       ƒ d{  –—† dkD  rV|j'                  d#d¬$«      ƒ d{  –—†  t        j                  d%«      ƒ d{  –—†  t        d&dj	                  |«      › �«       y|j                  d'«      j                   }|j#                  «       ƒ d{  –—† dkD  rV|j'                  d#d¬$«      ƒ d{  –—†  t        j                  d%«      ƒ d{  –—†  t        d(dj	                  |«      › �«       y|j                  d)«      j                   }|j#                  «       ƒ d{  –—† dkD  rV|j'                  d#d¬$«      ƒ d{  –—†  t        j                  d%«      ƒ d{  –—†  t        d*dj	                  |«      › �«       y	 y7 �ŒË7 �Œq7 �ŒW7 �ŒC# t        $ r}t        d|› d|› �«       Y d}~�Œ/d}~ww xY w7 �Œ'7 �Œ}7 �ŒC7 �Œä7 �Œª7 �ŒY7 �Œ7 �Œô7 �ŒÄ7 �Œ©7 �Œ�7 �ŒA7 �Œ&7 �Œ7 Œ½7 Œ¡7 Œ†# t        $ r}t        d+|› d,|› �«       Y d}~yd}~ww xY w­w)-uN   Expand one folder ONLY under the selected season â€” never search other years.r   r  Nrä   zHudl CLICK open: season=r  rK   TrÛ  Úparent_not_expandedç      Ð?Fz Hudl CLICK open (retry): season=z Classic folder click failed for rÞ   rÔ  r   ú Seasonr&   ú@.manage-library-right-column li:has(> a.jstree-anchor:has-text("rç  ú0#libraryView li:has(> a.jstree-anchor:has-text("z")), li.toggle:has-text("rD  z"Folder expand scoped miss: season=r
  z (season row not found)ú1:scope > ul > li:has(> a.jstree-anchor:has-text("ú'")), :scope > ul > li.toggle:has-text("ú#li:has(> a.jstree-anchor:has-text("z missing prefix part z folder=z not under this yearzC:scope > .toggler, :scope > span > .toggler, :scope > span .togglerr^  r‡  ru   zHudl CLICK + toggler: ré  zHudl CLICK jstree-ocl: z$:scope > a.jstree-anchor, :scope > azHudl CLICK label: z Season-scoped folder click for 'ú
' failed: )rÞ  r¹   r´   r^   r�  r<  rÃ  r0   r@  r-  r   r   r   Úreplacer*   r,   r-   r.   rR  r  )r#   rË  r]  r^  r  rÓ  rm   Úbaserá  râ  rñ  ÚscopeÚpartÚ	part_baseÚnestedÚ	folder_lirã  rï  rò  s                      r   Ú_expand_folder_on_pathrp    sÛ  è ø€ ð ˜F˜8Ñ#€KðGÜ/ØØØÜ ›K¨1Ñ,ô	
÷ 
ˆð �;‰;�tÔÜÐ,¨\Ð,<¸EÀ#Ç(Á(È;ÓBWÐAXÐYÔZØà�;‰;�xÓ Ð$9Ò9Ü)Ø�l F¸Dô÷ ð ô —-‘- Ó%×%Ð%Ü3Ø�l KÀô÷ ˆGð �{‰{˜4Ô ÜÐ8¸Ð8HÈÈcÏhÉhÐWbÓNcÐMdÐeÔfØô
 (¨¨l¸KÓH×HÐHÜÐ(¨Ð(8¸¸c¿h¹hÀ{Ó>SÐ=TÐUÔVØä˜vÓ&€DÜ—‘Ð+¨\Ó:€JÙ",ˆ:×Ñ˜AÔ°,×2FÑ2FÀyÐRTÓ2U×2[Ñ2[Ó2]€DðLJà—L‘LØNÈtÈfÐTWÐXó
ç
‰%ð 	ð —‘Ó"×" aÒ'ØŸ™ØBÀ4À&ð I'Ø'+ f¨Bð0ó÷ ‰eð ð —‘Ó"×" aÒ'ÜØ4°\Ð4Dð EØ#�Ð&=ð?ôð àˆÛˆDÜ+¨DÓ1ˆIØ—]‘]ØCÀIÀ;ð O5Ø5>°K¸rðCó÷ ‰eð ð —\‘\“^×# qÒ(ØŸ™Ø9¸)¸ð E+Ø+4¨+°Rð9ó÷ ‘%ð ð —\‘\“^×# qÒ(ÜØ8¸Ð8Hð I+Ø+4¨-ð9ôñ Ø‰Eð# ð& —M‘MØ?À¸vð F1Ø15°°bð:ó
÷ ‰%ð 	ð —‘Ó"×" aÒ'ØŸ™Ø5°d°Vð <'Ø'+ f¨Bð0ó÷ ‰eð ð —‘Ó"×" aÒ'ÜØ4°\Ð4Dð EØ˜Ð!5ð7ôð à×2Ñ2Ó4×4Ð4à×#Ñ#ØQó
ç
‰%ð 	ð —‘“×  1Ò$Ø—-‘-¨°D�-Ó9×9Ð9Ü—-‘- Ó$×$Ð$ÜÐ*¨3¯8©8°KÓ+@Ð*AÐBÔCØØ×ÑÐ 6Ó7×=Ñ=ˆØ—‘“×˜qÒ Ø—)‘) D°�)Ó5×5Ð5Ü—-‘- Ó$×$Ð$ÜÐ+¨C¯H©H°[Ó,AÐ+BÐCÔDØØ×"Ñ"Ð#IÓJ×PÑPˆØ—‘“× !Ò#Ø—,‘, t°4�,Ó8×8Ð8Ü—-‘- Ó$×$Ð$ÜÐ& s§x¡x°Ó'<Ð&=Ð>Ô?Øð	 $ð ð]
ùðùð &ùðúô ò GÜÐ0°°¸RÀ¸uÐE×FÒFûðGúð Iùð #ùð
 #ùð $ùð
 $ùð #ùð
 #ùð 	5ùð
 !ùØ9ùØ$ùð ùØ5ùØ$ùð  øØ8øØ$ùô ò JÜÐ0°°¸
À3À%ÐH×IÐIØûðJüs  ‚U>ŠS? ¨S3©5S? ÁU>Á&S? ÂS6ÂS? Â"S9Â#S? Â9S<Â:5S? Ã/U>Ä T'ÄA9U>Å;2U Æ-T*Æ.<U Ç*T-Ç+U ÈU>ÈAU ÉT0É<U Ê	T3Ê
U Ê#U>Ê$9U ËT6Ë<U ÌT9ÌU Ì4U>Ì5U ÍT<Í	2U Í;T?Í<U ÎUÎU Î6UÎ7!U ÏU>Ï.U ÐUÐU Ð%UÐ&U ÑUÑ!U Ñ$U>Ñ%.U ÒUÒU Ò1UÒ2U ÓUÓ!U Ó0U>Ó2U>Ó3S? Ó6S? Ó9S? Ó<S? Ó?	T$ÔTÔU>ÔT$Ô$U>Ô*U Ô-U Ô0U Ô3U Ô6U Ô9U Ô<U Ô?U ÕU ÕU ÕU ÕU ÕU ÕU ÕU ÕU Õ	U;Õ U6Õ1U>Õ6U;Õ;U>c              ƒ   óR  K  — | j                  d«      j                  j                  dd¬«      ƒ d{  –—†  t        | |«      ƒ d{  –—† }|j	                  d«      s!t        |j	                  d«      xs d«      dkD  rt        d	|›�«       yt        | |«      ƒ d{  –—†  y7 Œn7 Œ\7 Œ	­w)
z3Expand a season row only if it is not already open.z*#libraryView, .manage-library-right-columnrG   rK  rD   NrÍ  rÎ  r   u)   Season already open â€” skip year click: )r,   r-   rR   rÑ  r´   rµ   r^   rä  )r#   rË  rE   s      r   Ú_ensure_season_expandedrr  |  s¥   è ø€ à
�,‰,ÐCÓ
D×
JÑ
J×
SÑ
SØ %ð Tó ÷ ð ô )¨¨|Ó<×<€EØ‡y�y�Ô¤ E§I¡I¨lÓ$;Ò$@¸qÓ AÀAÒ EÜÐ9¸,Ð9IÐJÔKØÜ
" 4¨Ó
6×6Ñ6ðøð =øð 7ús4   ‚0B'²B!³B'ÁB#ÁAB'ÂB%ÂB'Â#B'Â%B'c              ƒ   ó  K  — t        |«      }	 | j                  d|› d�«      j                  }|j                  «       ƒ d{  –—† dkD  re|j                  d«      j                  }|j                  «       ƒ d{  –—† dkD  r/|j	                  d¬«      ƒ d{  –—†  t        dd	«      ƒ d{  –—†  y
| j                  d|› d�«      j                  }|j                  «       ƒ d{  –—† dk(  r"| j                  d|› d|› d�«      j                  }|j                  «       ƒ d{  –—† dk(  ry|j                  d«      ƒ d{  –—† xs d}d|v r™|j                  d«      j                  }|j                  «       ƒ d{  –—† dkD  r|j	                  d¬«      ƒ d{  –—†  n3|j                  d«      j                  j	                  d¬«      ƒ d{  –—†  t        dd	«      ƒ d{  –—†  y
d|v re|j                  d«      j                  }|j                  «       ƒ d{  –—† dkD  r/|j	                  d¬«      ƒ d{  –—†  t        dd	«      ƒ d{  –—†  y
d|v sd|v ry
|j                  d«      j                  }|j                  «       ƒ d{  –—† dkD  r/|j	                  d¬«      ƒ d{  –—†  t        dd	«      ƒ d{  –—†  y
	 y7 �Œx7 �ŒE7 �Œ+7 �Œ7 �Œå7 �Œ«7 �Œ‘7 �ŒY7 �Œ?7 �Œ7 Œü7 ŒÆ7 Œ«7 Œ™7 Œ^7 ŒC7 Œ1# t        $ r}	t        d|› d|	› �«       Y d}	~	yd}	~	ww xY w­w)z4Expand a jstree or toggle-tree folder node by label.zli.toggle.collapsed:has-text("rD  Nr   z.togglerr^  rq   rÝ  rr   Træ  rç  zli.toggle:has-text("z$"), li:has(.jstree-anchor:has-text("Frd  r&   rê  ré  rí  rë  rì  rÍ  zExpand jstree node 'rh  )	r-  r,   r-   r.   r  r   r/   r0   r^   )
r#   r�   rj  Ú	toggle_lirã  rî  rð  rï  rò  rm   s
             r   Ú_expand_jstree_noderu  ˆ  sÙ  è ø€ ä˜uÓ%€Dð-=Ø—L‘LÐ#AÀ$ÀÀrÐ!JÓK×QÑQˆ	Ø—‘Ó"×" QÒ&Ø×'Ñ'¨
Ó3×9Ñ9ˆGØ—]‘]“_×$ qÒ(Ø—m‘m¨D�mÓ1×1Ð1Ü! # sÓ+×+Ð+Øà�\‰\ØKÈDÈ6ÐQTÐUó
ç
‰%ð 	ð —‘“×˜qÒ Ø—‘Ø& t fÐ,PÐQUÐPVÐVYÐZóç‰eð ð —‘“×˜qÒ Øà×)Ñ)¨'Ó2×2Ò9°rˆØ˜gÑ%Ø—*‘*Ð3Ó4×:Ñ:ˆCØ—Y‘Y“[×  1Ò$Ø—i‘i¨�iÓ-×-Ñ-à—j‘jÐ!1Ó2×8Ñ8×>Ñ>ÀtÐ>ÓL×LÐLÜ˜c 3Ó'×'Ð'Øà˜'Ñ!Ø—j‘j Ó,×2Ñ2ˆGØ—]‘]“_×$ qÒ(Ø—m‘m¨D�mÓ1×1Ð1Ü! # sÓ+×+Ð+Øà˜GÑ# z°WÑ'<Øà—‘Ð,Ó-×3Ñ3ˆØ—‘“× !Ò#Ø—,‘, t�,Ó,×,Ð,Ü˜c 3Ó'×'Ð'Øð $ð ðY #ùà$ùØ1ùØ+ùð ùð ùð 3ùð !ùØ-ùàLùØ'øð
 %øØ1øØ+øð  øØ,øØ'ùäò =ÜÐ$ U G¨:°c°UÐ;×<Ð<Øûð=üs¢  ‚L�2K% ÁJ9Á5K% Á7J<Á8K% ÂJ?ÂK% Â(KÂ)K% Â-LÂ.2K% Ã KÃ!<K% ÄKÄK% Ä%LÄ&K% Ä:KÄ;:K% Å5KÅ6K% ÆKÆ3K% ÇKÇK% ÇKÇK% ÇLÇ 2K% ÈKÈK% È/KÈ0K% ÉKÉK% ÉLÉ	K% ÉLÉ.K% Ê KÊK% ÊK!ÊK% Ê1K#Ê2K% Ê6LÊ8LÊ9K% Ê<K% Ê?K% ËK% ËK% ËK% ËK% ËK% ËK% ËK% ËK% ËK% ËK% ËK% ËK% Ë!K% Ë#K% Ë%	L	Ë.LË?LÌL	Ì	Lc              ƒ   ó–   K  — 	 | j                  d|«      ƒ d{  –—† S 7 Œ# t        $ r}t        d|› d|› �«       g cY d}~S d}~ww xY w­w)zCParse direct children under an expanded jstree or toggle-tree node.aú  (label) => {
                const monthRe = /(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+\d{1,2}\b/i;
                const gluedMonthRe = /(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)(\d{1,2})\b/i;
                const trailingMonthRe = /(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)$/i;
                const hrRe = /([\d.]+)\s*hr\b/i;
                const stripCount = (s) => (s || '').replace(/\s*\(\d+\)\s*$/, '').trim();
                const normalize = (s) => stripCount(s).replace(monthRe, '').replace(gluedMonthRe, '').replace(trailingMonthRe, '').replace(hrRe, '').replace(/\s{2,}/g, ' ').trim();
                const target = normalize(label);

                const getLiLabel = (li) => {
                    const anchor = li.querySelector(':scope > a.jstree-anchor') || li.querySelector(':scope > a');
                    if (anchor) return (anchor.textContent || '').trim();
                    const clone = li.cloneNode(true);
                    clone.querySelector('.toggler')?.remove();
                    clone.querySelector('ul')?.remove();
                    return (clone.textContent || '').replace(/\s+/g, ' ').trim();
                };

                const isFolderLi = (li) => {
                    if (li.classList.contains('toggle')) return true;
                    if (li.classList.contains('jstree-closed') || li.classList.contains('jstree-open')) return true;
                    if (li.querySelector(':scope > .toggler, :scope > span .toggler')) return true;
                    if (li.querySelector(':scope > .jstree-ocl')) return true;
                    return false;
                };

                const isLiLeaf = (li) => {
                    if (isFolderLi(li)) return false;
                    if (li.classList.contains('jstree-leaf')) return true;
                    if (li.querySelector('.jstree-checkbox, input[type="checkbox"]')) return true;
                    return !li.querySelector(':scope > ul');
                };

                const lis = Array.from(document.querySelectorAll('li.toggle, li.jstree-node, li.jstree-leaf'));
                const li = lis.find((node) => {
                    const n = normalize(getLiLabel(node));
                    return n === target || n.includes(target) || target.includes(n);
                });
                if (!li) return [];
                const childUl = li.querySelector(':scope > ul');
                if (!childUl) return [];

                const parseMeta = (text, rowText) => {
                    const raw = (text || '').trim();
                    const combined = rowText || raw;
                    let name = stripCount(raw);
                    let count = null;
                    const m = raw.match(/\((\d+)\)\s*$/);
                    if (m) count = parseInt(m[1], 10);
                    name = name.replace(monthRe, '').replace(gluedMonthRe, '').replace(trailingMonthRe, '').replace(hrRe, '').replace(/\s{2,}/g, ' ').trim();
                    const fullDate = combined.match(monthRe);
                    const gluedDate = combined.match(gluedMonthRe);
                    const trailingDate = combined.match(trailingMonthRe);
                    const hrMatch = combined.match(hrRe);
                    return {
                        label: name,
                        date: fullDate ? fullDate[0] : gluedDate ? `${gluedDate[1]} ${gluedDate[2]}` : trailingDate ? trailingDate[0] : undefined,
                        hours: hrMatch ? parseFloat(hrMatch[1]) : undefined,
                        count,
                    };
                };

                return Array.from(childUl.querySelectorAll(':scope > li')).map((childLi, idx) => {
                    const rowText = (childLi.innerText || '').replace(/\s+/g, ' ').trim();
                    const text = getLiLabel(childLi);
                    const meta = parseMeta(text, rowText);
                    const nodeId =
                        childLi.id ||
                        childLi.getAttribute('data-cat-id') ||
                        childLi.getAttribute('data-categoryid') ||
                        `${target}-${idx}`;
                    if (isLiLeaf(childLi)) {
                        return {
                            id: String(nodeId),
                            label: meta.label,
                            date: meta.date,
                            selectable: true,
                            children: [],
                        };
                    }
                    return {
                        id: String(nodeId),
                        label: meta.label,
                        date: meta.date,
                        hours: meta.hours,
                        count: meta.count,
                        children: [],
                    };
                });
            }NzParse jstree children for 'rh  rÊ  )r#   r�   rm   s      r   Ú_parse_jstree_node_childrenrw  ¼  se   è ø€ ð`Ø—]‘]ðYðt ów\
÷ \
ð \	
ð \
ùôz ò ÜÐ+¨E¨7°*¸S¸EÐBÔCØ�	ûðüs:   ‚A	„  ™š  �A	ž   	A©A»A¼A	ÁAÁA	aM  
({ seasonLabel, folderPath }) => {
    const seasonYearRe = /(\d{4}-\d{4})/;
    const monthRe = /(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+\d{1,2}\b/i;
    const stripCount = (s) => (s || '').replace(/\s*\(\d+\)\s*$/, '').trim();
    const normalize = (s) => stripCount(s)
        .replace(/\s+Season$/i, '')
        .replace(monthRe, '')
        .replace(/[\d.]+\s*hr\b/i, '')
        .replace(/\s{2,}/g, ' ')
        .trim();
    const seasonKey = (s) => {
        const m = normalize(s).match(seasonYearRe);
        return m ? m[1] : normalize(s);
    };
    const labelsMatch = (a, b) => {
        const na = normalize(a);
        const nb = normalize(b);
        if (!na || !nb) return false;
        if (na === nb) return true;
        const ya = seasonKey(a);
        const yb = seasonKey(b);
        if (ya && yb && ya === yb) return true;
        return na.includes(nb) || nb.includes(na);
    };
    const getLiLabel = (li) => {
        const anchor = li.querySelector(':scope > a.jstree-anchor') || li.querySelector(':scope > a');
        if (anchor) return (anchor.textContent || '').trim();
        const clone = li.cloneNode(true);
        clone.querySelector('.toggler')?.remove();
        clone.querySelector('ul')?.remove();
        return (clone.textContent || '').replace(/\s+/g, ' ').trim();
    };
    const isOpen = (li) =>
        li.classList.contains('jstree-open') ||
        (li.classList.contains('toggle') && li.classList.contains('expanded'));

    const candidates = Array.from(document.querySelectorAll(
        '#libraryView li.toggle, .tree li.toggle, .tree ul.t2 > li, #libraryView ul.t2 > li'
    ));
    let current = null;
    let bestDepth = Infinity;
    const year = seasonKey(seasonLabel);
    for (const li of candidates) {
        const raw = getLiLabel(li);
        if (seasonKey(raw) !== year && !labelsMatch(raw, seasonLabel)) continue;
        let depth = 0;
        let p = li.parentElement;
        while (p) {
            if (p.matches && p.matches('li.toggle, li.jstree-node')) depth += 1;
            p = p.parentElement;
        }
        if (depth < bestDepth) { bestDepth = depth; current = li; }
    }
    if (!current) return { seasonFound: false, seasonOpen: false, openDepth: 0 };
    if (!isOpen(current)) return { seasonFound: true, seasonOpen: false, openDepth: 0 };

    let openDepth = 0;
    for (const part of (folderPath || [])) {
        const childUl = current.querySelector(':scope > ul');
        if (!childUl) break;
        let next = null;
        for (const child of childUl.querySelectorAll(':scope > li')) {
            if (labelsMatch(getLiLabel(child), part)) {
                next = child;
                break;
            }
        }
        if (!next || !isOpen(next)) break;
        current = next;
        openDepth += 1;
    }
    return { seasonFound: true, seasonOpen: true, openDepth };
}
c              ƒ   ó¦   K  — 	 | j                  t        ||dœ«      ƒ d{  –—† S 7 Œ# t        $ r}t        d|› �«       ddddœcY d}~S d}~ww xY w­w)u?   How much of season â†’ folders is already visibly open on Hudl.©r  r  NzOpen-path depth check failed: Fr   )ÚseasonFoundÚ
seasonOpenÚ	openDepth)rj   Ú_OPEN_PATH_DEPTH_JSr0   r^   )r#   rË  r  rm   s       r   Ú_count_open_folder_path_depthr~  n  sb   è ø€ ðKØ—]‘]ÜØ(¸ÑDó
÷ 
ð 	
ð 
ùô ò KÜÐ.¨s¨eÐ4Ô5Ø$°EÈÑJÕJûðKüs<   ‚A„'  %¡' ¤A¥' §	A°A	ÁAÁAÁ	AÁA©r$   rJ  rú  r  c                  ó:   — dt         d<   dt         d<   g t         d<   y ©Nr&   r$   rJ  rú  )r  rÊ   r   r   r  r  €  s    € Ø„JˆyÑØ„JˆxÑØ„JˆvÒr   rJ  rú  c                 ó‚   — t        | xs d«      j                  «       t        d<   |t        d<   t        |«      t        d<   y r�  )rd   r*   r  r²   r  s      r   r!  r!  †  s6   € Ü ¢¨2Ó.×4Ñ4Ó6„JˆyÑØ!„JˆxÑÜ˜d›„JˆvÒr   c           
   ƒ   óT  K  — t        | ||«      ƒ d{  –—† }t        |j                  d«      «      }t        dt	        t        |j                  d«      xs d«      t        |«      «      «      }t        j                  d«      |k(  }t        t        j                  d«      xs g «      }|rS|sQt        | |«      ƒ d{  –—† }t        |j                  d«      xs! t        |j                  d«      xs d«      dkD  «      }|rq|ro|t        |«      k  rad}	t        ||«      D ]I  \  }
}|
j                  «       j                  «       |j                  «       j                  «       k7  r n|	d	z  }	ŒK |	|kD  r|	}|rt        d
|›d|› dt        |«      › �«       nLt        | d|› �d¬«      ƒ d{  –—†  t        | |«      ƒ d{  –—†  t!        j"                  d«      ƒ d{  –—†  d}t        |d| «      }||d }|sAt        d|›ddj%                  |«      › �«       t'        t        j                  d«      ||«       y|D ]«  }dj%                  |g|¢|‘«      }t        | d|› �d¬«      ƒ d{  –—†  t)        | |||«      ƒ d{  –—† st+        d|› d|› d�«      ‚t!        j"                  d«      ƒ d{  –—†  	 | j-                  d|||gz   dœd¬«      ƒ d{  –—†  |j1                  |«       Œ­ t'        t        j                  d«      ||«       y7 �Œï7 �ŒK7 �Œe7 �ŒT7 �Œ:7 Œ°7 Œœ7 Œo7 ŒM# t.        $ r Y ŒVw xY w­w)uß   Expand only what is not already open â€” stay where Playwright already is.

    If year + parent folders are open, jump straight to the next unopened folder
    instead of re-checking the year from the top every click.
    Nr{  r   r|  rJ  rú  rÍ  rÎ  r   z%Sticky navigate: season already open z, openDepth=rK   r  Tr  rÜ  z#Sticky navigate: path already open r  r$   z
Clicking: zCould not expand folder 'z' inside season 'Ú'a;  ({ seasonLabel, folderPath }) => {
                    const seasonYearRe = /(\d{4}-\d{4})/;
                    const monthRe = /(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+\d{1,2}\b/i;
                    const stripCount = (s) => (s || '').replace(/\s*\(\d+\)\s*$/, '').trim();
                    const normalize = (s) => stripCount(s)
                        .replace(/\s+Season$/i, '')
                        .replace(monthRe, '')
                        .replace(/[\d.]+\s*hr\b/i, '')
                        .replace(/\s{2,}/g, ' ')
                        .trim();
                    const seasonKey = (s) => {
                        const m = normalize(s).match(seasonYearRe);
                        return m ? m[1] : normalize(s);
                    };
                    const labelsMatch = (a, b) => {
                        const na = normalize(a);
                        const nb = normalize(b);
                        if (!na || !nb) return false;
                        if (na === nb) return true;
                        const ya = seasonKey(a);
                        const yb = seasonKey(b);
                        if (ya && yb && ya === yb) return true;
                        return na.includes(nb) || nb.includes(na);
                    };
                    const getLiLabel = (li) => {
                        const anchor = li.querySelector(':scope > a.jstree-anchor') || li.querySelector(':scope > a');
                        if (anchor) return (anchor.textContent || '').trim();
                        const clone = li.cloneNode(true);
                        clone.querySelector('.toggler')?.remove();
                        clone.querySelector('ul')?.remove();
                        return (clone.textContent || '').replace(/\s+/g, ' ').trim();
                    };
                    const candidates = Array.from(document.querySelectorAll(
                        '#libraryView li.toggle, .tree li.toggle, .tree ul.t2 > li, #libraryView ul.t2 > li'
                    ));
                    let current = null;
                    let bestDepth = Infinity;
                    const year = seasonKey(seasonLabel);
                    for (const li of candidates) {
                        const raw = getLiLabel(li);
                        if (seasonKey(raw) !== year && !labelsMatch(raw, seasonLabel)) continue;
                        let depth = 0;
                        let p = li.parentElement;
                        while (p) {
                            if (p.matches && p.matches('li.toggle, li.jstree-node')) depth += 1;
                            p = p.parentElement;
                        }
                        if (depth < bestDepth) { bestDepth = depth; current = li; }
                    }
                    if (!current) return false;
                    for (const part of (folderPath || [])) {
                        const childUl = current.querySelector(':scope > ul');
                        if (!childUl) return false;
                        let next = null;
                        for (const child of childUl.querySelectorAll(':scope > li')) {
                            if (labelsMatch(getLiLabel(child), part)) {
                                next = child;
                                break;
                            }
                        }
                        if (!next) return false;
                        current = next;
                    }
                    const ul = current.querySelector(':scope > ul');
                    return !!(ul && ul.querySelectorAll(':scope > li').length > 0);
                }ry  rÁ  rq   )r~  r·   r´   Úmaxr8  rµ   r¹   r  r²   rÑ  Úzipr   r*   r^   r  rr  r<  rÃ  r�  r!  rp  rT   rx   r0   r¸   )r#   rË  r  Ú
open_stateÚseason_openÚ
open_depthÚsticky_seasonÚsticky_pathÚseason_stateÚcommonr  Úbr]  Ú	remainingr^  Ú
path_labels                   r   rH  rH  Œ  sG  è ø€ ô 5°T¸<ÈÓU×U€JÜ�z—~‘~ lÓ3Ó4€KÜ�QœœC 
§¡¨{Ó ;Ò @¸qÓAÄ3À{ÓCSÓTÓU€Jô —N‘N 8Ó,°Ñ<€MÜ”z—~‘~ fÓ-Ò3°Ó4€KÙ™[ä3°D¸,ÓG×GˆÜØ×Ñ˜ZÓ(ÒX¬C°×0@Ñ0@ÀÓ0NÒ0SÐRSÓ,TÐWXÑ,Xó
ˆñ ™¨´c¸+Ó6FÒ)FàˆÜ˜ [Ö1‰DˆAˆqØ�w‰w‹y�‰Ó  A§G¡G£I§O¡OÓ$5Ò5ÙØ�a‰K‰Fð 2ð �JÒØˆJáÜØ3°LÐ3Cð DØ#˜ A¤c¨+Ó&6Ð%7ð9õ	
ô
 & d¨n¸\¸NÐ,KÐSWÔX×XÐXÜ% d¨LÓ9×9Ð9Ü�m‰m˜CÓ × Ð Øˆ
ä˜[¨¨*Ð5Ó6€FØ˜J˜KÐ(€IáäØ1ØÐ˜e C§H¡H¨[Ó$9Ð#:ð<ô	
ô 	”z—~‘~ iÓ0°,ÀÔLØãˆØ—\‘\ <Ð"A°&Ð"A¸&Ð"AÓBˆ
Ü% d¨j¸¸Ð,EÈTÔR×RÐRÜ+¨D°,ÀÈÓO×OÐOÜØ+¨F¨8Ð3DÀ\ÀNÐRSÐTóð ô �m‰m˜CÓ × Ð ðH	Ø×(Ñ(ðAðD !-¸FÀfÀXÑ<MÑNØðI )ó E÷ Eð EðP 	�‰�fÕðe ôh ”:—>‘> )Ó,¨l¸KÕHðE Vùð Hùð( 	YùØ9ùØ ùð" 	SøØOøð 	!øðEùôL ò 	Ùð	üsÆ   ‚L(“L”B&L(Â:LÂ;C(L(Æ#LÆ$L(Æ7LÆ8L(ÇLÇBL(É LÉ!L(É6LÉ7.L(Ê%LÊ&L(Ê+LË	LË
LË5L(ÌL(ÌL(ÌL(ÌL(ÌL(ÌL(ÌL(ÌLÌ	L%Ì"L(Ì$L%Ì%L(c              ƒ   óÈ  ‡ ‡‡‡K  — |j                  «       Št        |xs g D �cg c]#  }|j                  «       sŒ|j                  «       ‘Œ% c}«      Š‰st        d«      ‚‰st        d«      ‚dt        fˆ ˆˆˆfd„}	 t	        j
                   |«       d¬«      ƒ d{  –—† S c c}w 7 Œ	# t        j                  $ r$}t        dd	j                  ‰«      › d
�«      |‚d}~ww xY w­w)z²Hierarchical drill-down on Hudl:
    1) open the selected YEAR
    2) search/open each folder ONLY inside that year
    3) deeper clicks search ONLY inside the parent folder
    r  zfolder_path is requiredr   c            
   “   ó  •K  — t        ‰‰«      ƒ d {  –—† } t        ‰«      ƒ d {  –—†  ‰d   }t        ‰‰‰«      ƒ d {  –—† }|r?t        d‰›d‰›dt	        |«      › d�«       t        | ‰‰«       d| ‰‰||t	        |«      ddœS t        ‰‰‰«      ƒ d {  –—†  t        ‰‰‰d	¬
«      ƒ d {  –—† }|st        ‰‰‰«      ƒ d {  –—† }t        d‰›d‰›dt	        |«      › d�«       d| ‰‰||t	        |«      dœS 7 Œæ7 ŒÕ7 Œ½7 Œi7 ŒT7 Œ?­w)Nr#  z(FAST PATH folder already visible season=r
  rÞ   u!    children â€” skip navigate/clickrä   T)r�   r$   rË  r  Úfolder_labelrû  r.   r  rN  r  zFolder drill-down season=z children (sticky depth used))r�   r$   rË  r  r“  rû  r.   )r'  r½  rD  r^   r¹   r!  rH  r\  )r  Útarget_folderÚexistingrû  r#   rú  rJ  r$   s       €€€€r   r&  z*expand_manage_library_folder.<locals>._run+  sX  øè ø€ Ü!<¸TÀ7Ó!K×KÐÜ+¨DÓ1×1Ð1à˜R™ˆô 7°t¸VÀTÓJ×JˆÙÜØ:¸6¸*ÀFÈ4È(ÐRTÜ�x“=�/Ð!BðDôô Ð+¨V°TÔ:àØ+Ø &Ø#Ø -Ø$Ü˜X›Ø!ñ	ð 	ô ,¨D°&¸$Ó?×?Ð?Ü8Ø�&˜$¨Qô
÷ 
ˆñ Ü:¸4ÀÈÓN×NˆHäØ'¨ z°¸°x¸rÜ�8‹}ˆoÐ:ð<ô	
ð Ø'Ø"ØØ)Ø Ü˜“]ñ
ð 	
ðK LøØ1øð
 Køð& 	@øð
øð Oúsh   ƒD“C:”D¦C<§DÁ C>ÁADÂD ÂDÂ-DÂ.DÃDÃ6DÃ<DÃ>DÄ DÄDÄDr'  rq   NzTimed out expanding folder 'rK   r(  )r*   r5  rT   r³   r<  rR   r?  r�  )	r#   r$   rË  r  r4  r&  rm   rú  rJ  s	   ``     @@r   Úexpand_manage_library_folderr–    sÜ   ûè ø€ ð ×ÑÓ!€FÜ!°{Ò7HÀbÐ7HÓ"WÑ7H°ÈQÏWÉWÍY 1§7¡7¥9Ð7HÑ"WÓX€DÙÜÐ3Ó4Ð4ÙÜÐ2Ó3Ð3ð.
œ÷ .
ð .
ð`Ü×%Ñ%¡d£f°dÔ;×;Ð;ùòo #Xðn <ùÜ×Ñò ÜØ*¨3¯8©8°D«>Ð*:¸*ÐEó
àð	ûðüsK   †C"¤B!ºB!Á0C"Á=B( ÂB&ÂB( Â C"Â&B( Â(CÂ;CÃCÃC"rF   c              ƒ   ó’   K  — 	 | j                  d|¬«      ƒ d{  –—†  t        dd«      ƒ d{  –—†  y7 Œ7 Œ# t        $ r Y yw xY w­w)z?Wait until Hudl shows the blue 'N Item(s) selected' action bar.zÇ() => {
                const text = (document.body.innerText || '').replace(/\s+/g, ' ');
                return /\d+\s+Items?\s+selected/i.test(text) || /Item\s+selected/i.test(text);
            }rq   Nçš™™™™™¹?rÜ  TF)rx   r   r0   ©r#   rF   s     r   Ú&_wait_for_manage_library_selection_barrš  c  sc   è ø€ ðØ×$Ñ$ðð ð %ó 
÷ 	
ð 	
ô ˜#˜sÓ#×#Ð#Øð	
øð 	$ùäò Ùðüs=   ‚A„8 š4›8 ®6¯8 ³A´8 ¶8 ¸	AÁAÁAÁAa  
() => {
    const isVisible = (el) => {
        if (!el) return false;
        const rect = el.getBoundingClientRect();
        const style = window.getComputedStyle(el);
        return (
            rect.width > 0 &&
            rect.height > 0 &&
            style.visibility !== 'hidden' &&
            style.display !== 'none' &&
            style.opacity !== '0'
        );
    };
    const isActionBarContainer = (node) => {
        if (!node || !node.innerText) return false;
        const text = node.innerText.replace(/\s+/g, ' ');
        return (
            /selected/i.test(text) &&
            (/Share/i.test(text) || /Unshare/i.test(text) || /Move/i.test(text))
        );
    };
    const tryClick = (el) => {
        if (!isVisible(el)) return false;
        el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }));
        if (typeof el.click === 'function') el.click();
        return true;
    };
    const nodes = Array.from(document.querySelectorAll('a, button, span, div, li'));
    for (const el of nodes) {
        if ((el.textContent || '').trim() !== 'Download') continue;
        let container = el;
        for (let depth = 0; depth < 10 && container; depth++) {
            if (isActionBarContainer(container)) {
                if (tryClick(el)) return { ok: true, method: 'action_bar_text' };
                break;
            }
            container = container.parentElement;
        }
    }
    for (const el of document.querySelectorAll('a, span, button')) {
        if ((el.textContent || '').trim() !== 'Download') continue;
        let parent = el.parentElement;
        while (parent) {
            const bg = window.getComputedStyle(parent).backgroundColor || '';
            if (bg.includes('102, 204') || bg.includes('0, 102, 204')) {
                if (tryClick(el)) return { ok: true, method: 'blue_bg' };
                break;
            }
            parent = parent.parentElement;
        }
    }
    return { ok: false, reason: 'download_link_not_found' };
}
c           	   ƒ   óô  K  — t        j                  d|«      }|r|j                  d«      n|j                  dd«      j	                  «       }| j                  d|› d|› d|› d�«      j                  }|j                  «       ƒ d	{  –—† d
k(  r| j                  d|› d�«      j                  }|j                  «       ƒ d	{  –—† d
k(  r| j                  d|› d�«      j                  }|j                  «       ƒ d	{  –—† d
k(  ry	|}|xs g D ]b  }t        |«      }|j                  d|› d|› d�«      j                  }	|	j                  «       ƒ d	{  –—† d
k(  rt        d|›d|›d|›�«        y	|	}Œd |S 7 Œë7 Œ³7 Œ{7 Œ)­w)zêReturn a Playwright locator scoped to the selected season (and optional path).

    Fail closed: if any path segment is missing, return None (never a parent
    scope) so download/select cannot pick a clip under the wrong folder.
    rÔ  r   rb  r&   z(#libraryView > ul > li.toggle:has-text("z&"), .tree ul.t2 > li.toggle:has-text("z"), li.toggle:has-text("z	 Season")Nr   rc  rç  rd  re  rf  rD  z#locator_within_season miss: season=z missing part=z under path=)
r   r   r   ri  r*   r,   r-   r.   r-  r^   )
r#   rË  r  rá  râ  rñ  rk  rl  rm  rn  s
             r   Ú_locator_within_seasonrœ  ¬  s¸  è ø€ ô —‘Ð+¨\Ó:€JÙ",ˆ:×Ñ˜AÔ°,×2FÑ2FÀyÐRTÓ2U×2[Ñ2[Ó2]€Dð —‘Ø
2°4°&ð 9-Ø-1¨Fð 3Ø#˜f Ið	/ó÷ �eð	 ð
 �_‰_Ó× !Ò#Ø—L‘LØNÈtÈfÐTWÐXó
ç
‰%ð 	ð �_‰_Ó× !Ò#Ø—L‘LØ>¸t¸fÀCÐHó
ç
‰%ð 	ð �_‰_Ó× !Ò#Øà€EØÒ!˜rÒ!ˆÜ'¨Ó-ˆ	à—‘Ø?À	¸{ð K1Ø1:°¸2ð?ó
÷ ‰%ð 	ð —‘“× 1Ò$ÜØ5°lÐ5Eð F Ø )˜}¨L¸¸ðIôñ Ø‰ð "ð €Lð5 øð øð øð  úsI   ‚BE8ÂE0Â9E8Â>E2Â?9E8Ã8E4Ã9AE8ÅE6Å$E8Å2E8Å4E8Å6E8c              ƒ   ó¦  K  — t        |«      }t        t        |«      «      }t        | ||d|¬«      ƒ d{  –—† }|j	                  d«      r;|j	                  d«      r*t        dd«      ƒ d{  –—†  t        | d¬	«      ƒ d{  –—† ry
t        | ||«      ƒ d{  –—† }|€y|j                  d|› d�«      j                  }|j                  «       ƒ d{  –—† dk(  r|j                  d|› d�«      j                  }	 |j                  «       ƒ d{  –—† dkD  r”|j                  «       ƒ d{  –—† r||j                  d«      j                  }	|	j                  «       ƒ d{  –—† dk(  r|}	|	j                  d¬	«      ƒ d{  –—†  t        dd«      ƒ d{  –—†  t        | d¬	«      ƒ d{  –—† ry
y7 �Œs7 �Œ@7 �Œ.7 �Œ7 Œã7 Œª7 Œ‘7 Œ`7 ŒC7 Œ17 Œ# t        $ r Y yw xY w­w)z@Fast path: select a clip only inside the selected season/folder.Úselect_clip©r6  Nrä   r5   r˜  rÜ  r\  rq   TFú"li:has(> a.jstree-anchor:text-is("rç  r   rg  zM.jstree-checkbox, ins.jstree-checkbox, input[type="checkbox"], .jstree-anchorrî  )r5  r2  r-  r<  r´   r   rš  rœ  r,   r-   r.   Ú
is_visibler  r0   )
r#   rË  r  r6  rú  rj  r  rk  Úclip_rowrÇ  s
             r   Ú!_quick_select_manage_library_clipr£  Ú  sÑ  è ø€ ô " +Ó.€DÜ!Ô"5°jÓ"AÓB€Dô )Øˆl˜D -¸Dô÷ €Fð ‡z�z�$Ô˜FŸJ™J zÔ2Ü˜#˜sÓ#×#Ð#Ü7¸ÀdÔK×KÐKØô )¨¨|¸TÓB×B€EØ€}ØØ�}‰}Ø
,¨T¨F°#Ð6óç�eð ð �^‰^Ó× Ò"Ø—=‘=Ø1°$°°sÐ;ó
ç
‰%ð 	ðØ—‘Ó!×! AÒ%°×0CÑ0CÓ0E×*EÐ*EØ×%Ñ%Ø_óç‰eð ð —\‘\“^×# qÒ(Ø!�Ø—,‘, t�,Ó,×,Ð,Ü˜c 3Ó'×'Ð'Ü;¸DÈ$ÔO×OÐOØð ðCùð 	$ùØKùð Cùð øð
 "øÐ*Eøð $øà,øØ'øØOùäò Øàðüsç   ‚3GµF(¶5GÁ+F+Á,GÂ F.ÂGÂF1Â9GÃF4Ã&GÃ8G ÄF6ÄG Ä&F8Ä'2G ÅF:ÅG Å8F<Å9G ÆF>ÆG Æ!G Æ"G Æ&GÆ+GÆ.GÆ1GÆ4GÆ6G Æ8G Æ:G Æ<G Æ>G Ç G Ç	GÇGÇGÇGc              ƒ   ó:  ‡ K  — t        |«      }t        t        |«      «      }t        ‰ ||d|¬«      ƒ d{  –—† }|j	                  d«      r;|j	                  d«      r*t        dd«      ƒ d{  –—†  t        ‰ d¬	«      ƒ d{  –—† ry
t        ‰ ||«      ƒ d{  –—† }|€y|j                  d|› d�«      j                  }|j                  «       ƒ d{  –—† dk(  r|j                  d|› d�«      j                  }|j                  «       ƒ d{  –—† dk(  r|j                  d|› d�«      j                  }dt        fˆ fd„}	|j                  «       ƒ d{  –—† dkD  r�dD ][  }
|j                  |
«      j                  }|j                  «       ƒ d{  –—† dkD  sŒ: |	|j                  d¬	«      «      ƒ d{  –—† sŒ[ y
  |	|j                  d¬	«      «      ƒ d{  –—† ry
t        d|›d|›d|›d|j	                  d«      › �«       y7 �ŒË7 �Œ˜7 �Œ†7 �Œs7 �Œ<7 �Œ7 ŒÁ7 Œˆ7 Œf7 ŒD­w)zBSelect a clip checkbox only under the selected season/folder path.rž  rŸ  Nrä   r5   r˜  rÜ  r^  rq   TFr   rç  r   rg  zli.jstree-leaf:has-text("rD  r   c              “   óž   •K  — 	 | ƒ d {  –—†  t        dd«      ƒ d {  –—†  t        ‰d¬«      ƒ d {  –—† S 7 Œ-7 Œ7 Œ# t        $ r Y yw xY w­w)Nr˜  rÜ  r^  rq   F)r   rš  r0   )Ú
click_coror#   s    €r   Ú_click_and_verifyz6_select_manage_library_clip.<locals>._click_and_verify  sS   øè ø€ ð	Ø×ÐÜ˜c 3Ó'×'Ð'Ü?ÀÈdÔS×SÐSð øØ'øØSùÜò 	Ùð	üsI   ƒA…> Š8‹> ž:Ÿ> ³<´> ·A¸> º> ¼> ¾	A
ÁAÁ	A
Á
A)z.jstree-checkboxzins.jstree-checkboxzi.jstree-checkboxzinput[type="checkbox"]rí  z Scoped clip select miss: season=r
  z clip=rC  rÛ  )r5  r2  r-  r<  r´   r   rš  rœ  r,   r-   r.   r·   r  r^   )r#   rË  r  r6  rú  rj  r  rk  r¢  r§  r?  rÇ  s   `           r   Ú_select_manage_library_clipr¨    s  øè ø€ ô " +Ó.€DÜ!Ô"5°jÓ"AÓB€Dä(Øˆl˜D -¸Dô÷ €Fð ‡z�z�$Ô˜FŸJ™J zÔ2Ü˜#˜sÓ#×#Ð#Ü7¸ÀdÔK×KÐKØä(¨¨|¸TÓB×B€EØ€}Øà�}‰}ÐAÀ$ÀÀsÐKÓL×RÑR€HØ�^‰^Ó× Ò"Ø—=‘=Ð#FÀtÀfÈCÐ!PÓQ×WÑWˆØ�^‰^Ó× Ò"Ø—=‘=Ð#<¸T¸FÀ"Ð!EÓF×LÑLˆð¬tõ ð �^‰^Ó× Ò!ó
ˆCð ×%Ñ% cÓ*×0Ñ0ˆFØ—\‘\“^×# aÓ'Ù*¨6¯<©<À¨<Ó+EÓF×FÑFÙð
ñ # 8§>¡>¸$ >Ó#?Ó@×@Ð@Øä	Ø
*¨<Ð*:¸&ÀÀð IØˆx�x §
¡
¨8Ó 4Ð5ð	7ôð ð]ùð 	$ùØKùð Cùð
 ùàùð øð $øØFøð Aús¾   ƒ3H¶H·5HÁ,HÁ-HÂHÂHÂH
Â9HÃHÃ9HÄHÄAHÅHÅ:HÆHÆHÆHÆ0HÆ1HÆ6HÇHÇ-HÈHÈHÈ
HÈHÈHÈHÈHÈHÈHc              ƒ   ó¢  K  — t        | d¬«      ƒ d{  –—† st        d«       | j                  d«      j                  d«      j                  dd¬	«      j                  | j                  d
«      j                  dd¬	«      j                  | j                  d«      j                  ddd¬«      j                  | j                  d«      j                  g}t        |«      D ]z  \  }}	 |j                  «       ƒ d{  –—† dkD  rY|j                  «       ƒ d{  –—† rA|j                  d¬«      ƒ d{  –—†  t        d|dz   › �«       t        dd«      ƒ d{  –—†   yŒ| t        d«      D ]‚  }	 | j                  t        «      ƒ d{  –—† }|j                  d«      r3t        d|j                  d«      › �«       t        dd«      ƒ d{  –—†   y	 t!        j"                  d«      ƒ d{  –—†  Œ„ y7 �ŒÖ7 Œö7 ŒÝ7 ŒÅ7 Œ¢# t        $ r}t        d|dz   › d|› �«       Y d}~�Œ<d}~ww xY w7 ŒŸ7 Œ_# t        $ r}t        d|dz   › d|› �«       Y d}~Œ|d}~ww xY w7 Œk­w)z0Click Download in the blue selection action bar.rM  rq   Nz?Manage Library: selection bar not visible before Download clickztext=/\d+ Items? selected/izxpath=ancestor::div[1]ÚDownloadTr§  zdiv:has-text("selected")r·  r<  )r‡   r¨  z1div:has-text("selected") >> a:text-is("Download")r   r^  zCManage Library: clicked action bar Download via Playwright attempt r   rv   rt   z'Action bar Download Playwright attempt z	 failed: r³  rä   z0Manage Library: clicked action bar Download via r¡  zAction bar Download JS attempt rÜ  F)rš  r^   r,   r­  r-   r¬  rï   r.   r¡  r  r   r0   r…  rj   Ú_CLICK_ACTION_DOWNLOAD_JSr´   r<  rÃ  )r#   Úplaywright_locatorsrù   r  rm   r“  r  s          r   Ú%_click_manage_library_action_downloadr­  >  s.  è ø€ ä7¸ÀeÔL×LÐLÜÐOÔPð 	�‰Ð3Ó4ß	‰Ð)Ó	*ß	‰�Z tˆÓ	,ß	‰Ø�‰Ð/Ó0×<Ñ<¸ZÈtÐ<ÓT×ZÑZØ�‰Ð3Ó4ß	‰�V *°DˆÓ	9ß	‰Ø�‰ÐHÓI×OÑOð
Ðô Ð/Ö0‰ˆˆ3ð	SØ—Y‘Y“[×  1Ò$¨s¯~©~Ó/?×)?Ð)?Ø—i‘i¨�iÓ-×-Ð-ÜÐ[Ð\]Ð`aÑ\aÐ[bÐcÔdÜ! # sÓ+×+Ð+Ùøð 1ô ˜–8ˆð	QØŸ=™=Ô)BÓC×CˆFØ�z‰z˜$ÔÜÐHÈÏÉÐT\ÓI]ÐH^Ð_Ô`Ü! # sÓ+×+Ð+Ùð  ô �m‰m˜CÓ × Ñ ð ð ðG Mùð  !øÐ)?øØ-øà+ùäò 	SÜÐ;¸AÀ¹E¸7À)ÈCÈ5ÐQ×RÒRûð	Súð
 Døð ,ùäò 	QÜÐ3°G¸a±K°=À	È#ÈÐO×PÑPûð	Qúà úsÝ   ‚I“G)”CIÃ"G4Ã5G,Ã6G4ÄG.ÄG4Ä*G0Ä+$G4ÅG2ÅG4ÅIÅ'H#Å?HÆ AH#ÇH!ÇH#ÇIÇ!IÇ"IÇ,G4Ç.G4Ç0G4Ç2G4Ç4	HÇ=HÈIÈHÈIÈH#È!H#È#	I
È,IÉ IÉI
É
Iu“	  
() => {
    const isVisible = (el) => {
        if (!el) return false;
        const rect = el.getBoundingClientRect();
        const style = window.getComputedStyle(el);
        return (
            rect.width > 20 &&
            rect.height > 20 &&
            style.display !== 'none' &&
            style.visibility !== 'hidden' &&
            parseFloat(style.opacity || '1') > 0.05
        );
    };

    // Hudl Classic Manage Library uses #downloadSettingsDlg.modalPopup
    const classic = document.querySelector('#downloadSettingsDlg, #beginDownloadButton');
    if (classic) {
        const modal =
            classic.id === 'downloadSettingsDlg'
                ? classic
                : classic.closest('#downloadSettingsDlg, .modalPopup') || classic;
        if (isVisible(modal) || isVisible(document.querySelector('#beginDownloadButton'))) {
            return true;
        }
        // Modal may be display:block but small until animation finishes â€” treat presence + modalBG as open
        const bg = document.querySelector('#modalBG.modalBackground, #modalBG');
        if (bg && isVisible(bg) && document.querySelector('#downloadSettingsDlg')) {
            return true;
        }
    }

    for (const modal of document.querySelectorAll(
        '#downloadSettingsDlg, .modalPopup, .modal, [role="dialog"], .ui-dialog'
    )) {
        const text = modal.innerText || '';
        if (!text.includes('Choose a Download Format') && modal.id !== 'downloadSettingsDlg') {
            continue;
        }
        if (!isVisible(modal)) continue;
        if (
            modal.id === 'downloadSettingsDlg' ||
            modal.classList.contains('modalPopup') ||
            modal.classList.contains('in') ||
            modal.classList.contains('show') ||
            modal.getAttribute('aria-hidden') === 'false'
        ) {
            return true;
        }
    }

    for (const heading of document.querySelectorAll('h1, h2, h3, .modal-title, .header')) {
        if (!(heading.textContent || '').includes('Choose a Download Format')) continue;
        if (!isVisible(heading)) continue;
        const modal =
            heading.closest('#downloadSettingsDlg') ||
            heading.closest('.modalPopup') ||
            heading.closest('.modal') ||
            heading.closest('[role="dialog"]') ||
            heading.closest('.ui-dialog');
        if (modal && isVisible(modal)) return true;
    }
    return false;
}
a  
({ downloadFormat, includeBreakdown }) => {
    const isVisible = (el) => {
        if (!el) return false;
        const rect = el.getBoundingClientRect();
        const style = window.getComputedStyle(el);
        return (
            rect.width > 20 &&
            rect.height > 20 &&
            style.display !== 'none' &&
            style.visibility !== 'hidden' &&
            parseFloat(style.opacity || '1') > 0.05
        );
    };

    const isDownloadLabel = (text) =>
        /^Download$/i.test((text || '').replace(/\s+/g, ' ').trim());

    const findVisibleModal = () => {
        const byId = document.querySelector('#downloadSettingsDlg');
        if (byId) {
            if (isVisible(byId) || isVisible(document.querySelector('#beginDownloadButton'))) {
                return byId;
            }
            const bg = document.querySelector('#modalBG');
            if (bg && isVisible(bg)) return byId;
            // Classic modal can stay in DOM with non-zero size even when partially ready
            const style = window.getComputedStyle(byId);
            if (style.display !== 'none' && style.visibility !== 'hidden') return byId;
        }

        for (const modal of document.querySelectorAll(
            '.modalPopup, .modal, [role="dialog"], .ui-dialog'
        )) {
            const text = modal.innerText || '';
            if (!text.includes('Choose a Download Format')) continue;
            if (!isVisible(modal)) continue;
            return modal;
        }

        for (const heading of document.querySelectorAll('h1, h2, h3, .modal-title, .header')) {
            if (!(heading.textContent || '').includes('Choose a Download Format')) continue;
            if (!isVisible(heading)) continue;
            const modal =
                heading.closest('#downloadSettingsDlg') ||
                heading.closest('.modalPopup') ||
                heading.closest('.modal') ||
                heading.closest('[role="dialog"]') ||
                heading.closest('.ui-dialog') ||
                heading.closest('.modal-dialog')?.parentElement;
            if (modal && isVisible(modal)) return modal;
        }
        return null;
    };

    const findDownloadButton = (modal) => {
        const byId = document.querySelector('#beginDownloadButton');
        if (byId && (isVisible(byId) || byId.offsetParent !== null || byId.getClientRects().length)) {
            return byId;
        }

        const roots = [modal];
        const dialog = modal.querySelector('.modal-dialog, .modal-content, .footer');
        if (dialog) roots.push(dialog);
        const footer = modal.querySelector('.modal-footer, .footer');
        if (footer) roots.unshift(footer);

        for (const root of roots) {
            for (const btn of root.querySelectorAll(
                'button, a, input[type="submit"], input[type="button"], .btn, [role="button"]'
            )) {
                const text = (
                    btn.textContent ||
                    btn.value ||
                    btn.getAttribute('aria-label') ||
                    ''
                ).replace(/\s+/g, ' ').trim();
                if (!isDownloadLabel(text)) continue;
                if (!isVisible(btn) && btn.id !== 'beginDownloadButton') continue;
                return btn;
            }
        }
        return null;
    };

    const ensureCheckboxChecked = (checkbox) => {
        if (!checkbox || checkbox.disabled) return false;
        if (checkbox.checked) return true;
        checkbox.focus();
        checkbox.click();
        if (!checkbox.checked) {
            checkbox.checked = true;
            checkbox.dispatchEvent(new Event('input', { bubbles: true }));
            checkbox.dispatchEvent(new Event('change', { bubbles: true }));
        }
        return !!checkbox.checked;
    };

    const modal = findVisibleModal();
    if (!modal) return { ok: false, reason: 'visible_modal_not_found' };

    const formatLabels = {
        multiple: 'Multiple Files per Playlist',
        single: 'Single File per Playlist',
        presentation: 'Presentation',
    };
    const targetLabel = formatLabels[downloadFormat] || formatLabels.multiple;

    const clickByText = (root, text) => {
        for (const el of root.querySelectorAll('label, span, div, li, input[type="radio"]')) {
            const value = (el.textContent || el.value || '').trim();
            if (!value.includes(text)) continue;
            if (el.tagName === 'INPUT') {
                el.checked = true;
                el.dispatchEvent(new Event('change', { bubbles: true }));
                el.click();
                return true;
            }
            const input = el.querySelector('input[type="radio"]');
            if (input) {
                input.checked = true;
                input.dispatchEvent(new Event('change', { bubbles: true }));
                input.click();
                return true;
            }
            el.click();
            return true;
        }
        return false;
    };

    clickByText(modal, targetLabel);

    let breakdownChecked = false;
    if (includeBreakdown && downloadFormat !== 'presentation') {
        // Prefer the checkbox under the selected "Multiple/Single Files" option
        const checkboxes = Array.from(modal.querySelectorAll('input[type="checkbox"]'));
        for (const cb of checkboxes) {
            if (cb.disabled) continue;
            const label = cb.closest('label') || cb.parentElement;
            const nearby = (label?.textContent || cb.nextSibling?.textContent || '').trim();
            const rowText = (cb.closest('div, li, tr, label')?.textContent || nearby).trim();
            if (rowText.includes('Include breakdown data')) {
                breakdownChecked = ensureCheckboxChecked(cb);
                if (breakdownChecked) break;
            }
        }
        if (!breakdownChecked) {
            for (const cb of checkboxes) {
                if (cb.disabled) continue;
                breakdownChecked = ensureCheckboxChecked(cb);
                if (breakdownChecked) break;
            }
        }
    }

    const downloadBtn = findDownloadButton(modal);
    if (!downloadBtn) return { ok: false, reason: 'download_button_not_found' };

    downloadBtn.dispatchEvent(
        new MouseEvent('click', { bubbles: true, cancelable: true, view: window })
    );
    downloadBtn.click();
    return {
        ok: true,
        method: modal.id === 'downloadSettingsDlg' ? 'classic_downloadSettingsDlg' : 'visible_modal',
        breakdownChecked: includeBreakdown ? breakdownChecked : null,
        downloadButtonId: downloadBtn.id || null,
    };
}
aÕ  
() => {
    const isVisible = (el) => {
        if (!el) return false;
        const rect = el.getBoundingClientRect();
        const style = window.getComputedStyle(el);
        return (
            rect.width > 4 &&
            rect.height > 4 &&
            style.display !== 'none' &&
            style.visibility !== 'hidden' &&
            parseFloat(style.opacity || '1') > 0.05
        );
    };

    const clickEl = (el) => {
        el.dispatchEvent(
            new MouseEvent('click', { bubbles: true, cancelable: true, view: window })
        );
        if (typeof el.click === 'function') el.click();
    };

    const looksLikeClose = (el) => {
        const label = (
            (el.getAttribute && (el.getAttribute('aria-label') || el.getAttribute('title'))) || ''
        ).toLowerCase();
        const cls = (el.className && el.className.toString ? el.className.toString() : '').toLowerCase();
        const txt = (el.textContent || '').trim();
        return (
            label.includes('close') ||
            label.includes('dismiss') ||
            cls.includes('close') ||
            cls.includes('dismiss') ||
            cls.includes('uni-dismiss') ||
            txt === '\u00d7' ||
            txt === '\u2715' ||
            txt === 'x' ||
            txt === 'X'
        );
    };

    // Prefer Hudl's notification wrapper from the Manage Library DOM.
    const wrappers = Array.from(
        document.querySelectorAll(
            '.manage-library-notification-wrapper, [class*="manage-library-notification"]'
        )
    );
    for (const wrapper of wrappers) {
        if (!isVisible(wrapper)) continue;
        const text = (wrapper.textContent || '');
        if (!text.includes('email you a link') && !text.includes('ready to download')) {
            continue;
        }
        const closes = wrapper.querySelectorAll(
            'button.uni-dismiss, .uni-dismiss, button, a, span, i, [role="button"], [class*="close"], [class*="dismiss"]'
        );
        for (const el of closes) {
            if (!looksLikeClose(el) && el.tagName !== 'BUTTON') continue;
            if (!isVisible(el)) continue;
            // Prefer true dismiss controls over random buttons.
            if (looksLikeClose(el) || closes.length === 1) {
                clickEl(el);
                return { ok: true, method: 'notification_wrapper_close' };
            }
        }
        // Last resort inside the wrapper: right-most compact clickable.
        let best = null;
        let bestRight = -Infinity;
        for (const el of closes) {
            if (!isVisible(el)) continue;
            const txt = (el.textContent || '').trim();
            if (txt.length > 3 && !looksLikeClose(el)) continue;
            const rect = el.getBoundingClientRect();
            if (rect.right > bestRight) {
                bestRight = rect.right;
                best = el;
            }
        }
        if (best) {
            clickEl(best);
            return { ok: true, method: 'notification_wrapper_rightmost' };
        }
    }

    // Fallback: any visible banner with the confirmation text.
    const BANNER_TEXT = 'email you a link';
    let banner = null;
    for (const el of document.querySelectorAll(
        '.flash, .flashMessage, .notification, .alert, .toast, .banner, [role="alert"], [class*="flash"], [class*="notif"], [class*="banner"]'
    )) {
        const text = (el.textContent || '').trim();
        if (!text.includes(BANNER_TEXT)) continue;
        if (!isVisible(el)) continue;
        if (!banner || banner.contains(el)) banner = el;
    }
    if (!banner) return { ok: false, reason: 'banner_not_found' };

    for (const el of banner.querySelectorAll(
        'button, a, span, i, svg, [role="button"], .close, .uni-dismiss, [class*="close"], [class*="dismiss"]'
    )) {
        if (!looksLikeClose(el)) continue;
        if (!isVisible(el)) continue;
        clickEl(el);
        return { ok: true, method: 'banner_close_icon' };
    }

    return { ok: false, reason: 'close_icon_not_found' };
}
a^  
() => {
    const isVisible = (el) => {
        if (!el) return false;
        const rect = el.getBoundingClientRect();
        const style = window.getComputedStyle(el);
        return (
            rect.width > 4 &&
            rect.height > 4 &&
            style.display !== 'none' &&
            style.visibility !== 'hidden' &&
            parseFloat(style.opacity || '1') > 0.05
        );
    };

    const clickEl = (el) => {
        el.dispatchEvent(
            new MouseEvent('click', { bubbles: true, cancelable: true, view: window })
        );
        if (typeof el.click === 'function') el.click();
    };

    // Exact Hudl Classic selector from DevTools.
    const byId = document.querySelector('#manageLibraryActionBarDismiss');
    if (byId && isVisible(byId)) {
        clickEl(byId);
        return { ok: true, method: 'manageLibraryActionBarDismiss' };
    }

    const byClass = document.querySelector(
        '#manageLibraryActionBar button.uni-actionbar__dismiss, #manageLibraryActionBar .uni-dismiss, '
        + '.uni-actionbar__dismiss, .uni-dismiss'
    );
    if (byClass && isVisible(byClass)) {
        clickEl(byClass);
        return { ok: true, method: 'uni-actionbar__dismiss' };
    }

    // Locate the blue action bar: by id, or by the "N item(s) selected" text.
    let bar = document.querySelector(
        '#manageLibraryActionBar.uni-actionbar--is-open, #manageLibraryActionBar, .uni-actionbar--is-open, .uni-actionbar'
    );
    if (!bar || !isVisible(bar)) {
        const all = Array.from(document.querySelectorAll('div, section, header, nav'));
        for (const node of all) {
            if (!isVisible(node)) continue;
            const text = (node.innerText || '').replace(/\s+/g, ' ').trim();
            if (/\d+\s+items?\s+selected/i.test(text) || /item\s+selected/i.test(text)) {
                // Pick the tightest matching container (smallest that still holds the text).
                bar = node;
            }
        }
    }
    if (!bar || !isVisible(bar)) {
        return { ok: false, reason: 'selection_bar_not_found' };
    }

    const actionWords = ['share', 'unshare', 'download', 'copy', 'move', 'delete'];

    // Collect visible clickable candidates inside the bar.
    const candidates = Array.from(
        bar.querySelectorAll('button, a, [role="button"], .uni-dismiss, svg, i, span')
    ).filter(isVisible);

    // 1) Anything that explicitly looks like a dismiss/close control.
    for (const el of candidates) {
        const cls = String(el.className || '');
        const aria = (el.getAttribute && (el.getAttribute('aria-label') || el.getAttribute('title'))) || '';
        if (
            el.id === 'manageLibraryActionBarDismiss' ||
            /dismiss|close/i.test(cls) ||
            /dismiss|close/i.test(aria)
        ) {
            const target = el.closest('button, a, [role="button"]') || el;
            clickEl(target);
            return { ok: true, method: 'actionbar_dismiss_match' };
        }
    }

    // 2) Otherwise click the right-most icon-only control (the "X" cross) that
    //    is NOT one of the labelled action buttons.
    const clickable = Array.from(
        bar.querySelectorAll('button, a, [role="button"]')
    ).filter(isVisible);

    let best = null;
    let bestRight = -Infinity;
    for (const el of clickable) {
        const txt = (el.textContent || '').trim().toLowerCase();
        if (actionWords.includes(txt)) continue;
        // Icon-only close buttons usually have little/no text.
        if (txt.length > 2 && !/^[x\u00d7\u2715\u2716]$/i.test(txt)) continue;
        const right = el.getBoundingClientRect().right;
        if (right > bestRight) {
            bestRight = right;
            best = el;
        }
    }
    if (best) {
        clickEl(best);
        return { ok: true, method: 'actionbar_rightmost_icon' };
    }

    return { ok: false, reason: 'close_icon_not_found' };
}
c              ƒ   ó:  K  — t        d«      D �]@  }	 | j                  d«      }|j                  «       ƒ d{  –—† dkD  rq|j                  j	                  «       ƒ d{  –—† rO|j                  j                  dd¬«      ƒ d{  –—†  t        j                  d«      ƒ d{  –—†  t        d	«        y	 | j                  t        «      ƒ d{  –—† }|j                  d«      r=t        j                  d«      ƒ d{  –—†  t        d|j                  dd«      › �«        y|j                  d«      dk(  r yt        j                  d«      ƒ d{  –—†  �ŒC t        d«       y7 �Œ'7 �Œ7 Œâ7 ŒÇ# t        $ r Y ŒÃw xY w7 Œ­# t        $ r}d
d|› �dœ}Y d}~ŒÁd}~ww xY w7 ŒŸ7 ŒN­w)z‡Close Hudl's blue selection action bar via #manageLibraryActionBarDismiss.

    Best-effort only: logged and swallowed on failure.
    r³  z#manageLibraryActionBarDismissNr   rÁ  Tr‡  rÂ  zJManage Library: dismissed selection bar via #manageLibraryActionBarDismissFúevaluate_error:rÚ  rä   z,Manage Library: dismissed selection bar via r¡  ÚjsrÛ  Úselection_bar_not_foundra  z9Manage Library: selection bar not dismissed (best-effort))r…  r,   r.   r-   r¡  r  r<  rÃ  r^   r0   rj   Ú_DISMISS_SELECTION_BAR_JSr´   )r#   r“  Údismissr  rm   s        r   Ú%_dismiss_manage_library_selection_barr´  6  sv  è ø€ ô ˜—8ˆð
	Ø—l‘lÐ#CÓDˆGØ—]‘]“_×$ qÒ(°7·=±=×3KÑ3KÓ3M×-MÐ-MØ—m‘m×)Ñ)°$¸dÐ)ÓC×CÐCÜ—m‘m DÓ)×)Ð)ÜØ`ôñ ð	FØŸ=™=Ô)BÓC×CˆFð �:‰:�dÔÜ—-‘- Ó%×%Ð%ÜØ>¸v¿z¹zÈ(ÐTXÓ?YÐ>ZÐ[ôñ Ø�:‰:�hÓÐ#<Ò<ÙÜ�m‰m˜DÓ!×!Ò!ð5 ô8 
Ð
EÕFð3 %ùÐ-MùØCøØ)ùô
 ò 	Ùð	úð DùÜò 	FØ!¨¸s¸eÐ-DÑE�Fûð	Fúð &øð 	"úsÉ   ‚F“$E(·E¸$E(ÁE!Á$E(ÂE$ÂE(ÂE&ÂE(Â.FÂ1E9Ã	E7Ã
E9Ã)FÃ7FÃ8AFÅ
FÅFÅE(Å!E(Å$E(Å&E(Å(	E4Å1FÅ3E4Å4FÅ7E9Å9	FÆFÆ
FÆFÆFÆFc              ƒ   óî  K  — t        d«      D �]‡  }	 | j                  d«      }|j                  «       ƒ d{  –—† dkD  r¸|j                  j	                  «       ƒ d{  –—† r–|j                  j                  d«      }|j                  «       ƒ d{  –—† }t        |«      D ]U  }|j                  |«      }|j	                  «       ƒ d{  –—† sŒ-	 |j                  dd¬«      ƒ d{  –—†  t        d	«         y 	 | j                  t        «      ƒ d{  –—† }|j                  d«      r t        d|j                  dd«      › �«        y|j                  d«      dk(  rt        j                  d«      ƒ d{  –—†  �Œkt        j                  d«      ƒ d{  –—†  �ŒŠ t        d«       y7 �Œn7 �ŒL7 �Œ7 Œç7 ŒÌ# t        $ r Y �Œw xY w# t        $ r Y ŒÖw xY w7 ŒÀ# t        $ r}d
d|› �dœ}Y d}~ŒÔd}~ww xY w7 Œ~7 Œa­w)uÖ   Close Hudl's green "we'll email you a link" confirmation banner (Ã— icon).

    Best-effort only: the download is already triggered, so any failure here is
    logged and swallowed rather than failing the job.
    r³  z$.manage-library-notification-wrapperNr   zkbutton.uni-dismiss, .uni-dismiss, button[class*='close'], button[class*='dismiss'], button, [role='button']iÜ  Tr‡  zBManage Library: dismissed download banner via notification wrapperFr¯  rÚ  rä   z.Manage Library: dismissed download banner via r¡  r°  rÛ  Úbanner_not_foundra  rÜ  zHManage Library: download confirmation banner not dismissed (best-effort))r…  r,   r.   r-   r¡  rQ  r  r^   r0   rj   Ú_DISMISS_DOWNLOAD_BANNER_JSr´   r<  rÃ  )	r#   r“  Úwrapperr—  r.   rù   rD  r  rm   s	            r   Ú'_dismiss_manage_library_download_bannerr¹  [  sÙ  è ø€ ô ˜—8ˆð	Ø—l‘lÐ#IÓJˆGØ—]‘]“_×$ qÒ(°7·=±=×3KÑ3KÓ3M×-MÐ-MØ#ŸM™M×1Ñ1ðHó�	ð (Ÿo™oÓ/×/�Ü˜už�AØ#Ÿ-™-¨Ó*�CØ!$§¡Ó!1×1Ð1Ø ð!Ø!Ÿi™i°¸D˜iÓA×AÐAÜØ`ôò ð &ð	FØŸ=™=Ô)DÓE×EˆFð �:‰:�dÔÜØ@ÀÇÁÈHÐVZÓA[Ð@\Ð]ôñ à�:‰:�hÓÐ#5Ò5Ü—-‘- Ó%×%Ð%ÙÜ�m‰m˜CÓ × Ò ðO ôR 
Ð
TÕUðK %ùÐ-Mùð
 0ùð 2øð Bùô
 %ò !Ú ð!ûäò 	Ùð	úð FùÜò 	FØ!¨¸s¸eÐ-DÑE�Fûð	Fúð &øà ús  ‚G5“$G·F%¸$GÁF(Á2GÂF+Â6GÃF.ÃGÃF2Ã#F0Ã$F2Ã3GÃ4G5Ã6GÃ8GÄGÄGÄAG5Å2G1Å3G5ÆG3ÆG5Æ%GÆ(GÆ+GÆ.GÆ0F2Æ2	F?Æ;GÆ>F?Æ?GÇ	GÇG5ÇGÇG5ÇGÇ	G.ÇG)Ç$G5Ç)G.Ç.G5Ç3G5c              ƒ   ón   K  — 	 | j                  t        |¬«      ƒ d{  –—†  y7 Œ# t        $ r Y yw xY w­w)zVWait until Hudl's download format modal is actually visible (not the hidden template).rq   NTF)rx   Ú_WAIT_VISIBLE_DOWNLOAD_MODAL_JSr0   r™  s     r   Ú _wait_for_visible_download_modalr¼  �  sK   è ø€ ðØ×$Ñ$Ü+Øð %ó 
÷ 	
ð 	
ð ð		
ùô
 ò Ùðüs(   ‚5„& ž$Ÿ& £5¤& ¦	2¯5±2²5Údownload_formatÚinclude_breakdownc              ƒ   óx  K  — ||dœ}d}t        | d¬«      ƒ d{  –—† s3t        d«       t        | «      ƒ d{  –—†  t        | d¬«      ƒ d{  –—†  | j                  d«      }|j	                  «       ƒ d{  –—† d	kD  �rÉ	 |j                  d
d¬«      ƒ d{  –—†  	 |dk(  r|j                  dd¬«      j                  }n@|dk(  r|j                  dd¬«      j                  }n|j                  dd¬«      j                  }|j                  dd¬«      ƒ d{  –—†  t        dd«      ƒ d{  –—†  |r?|dv r;|j                  d«      ƒ d{  –—† }t        d|› �«       t        dd«      ƒ d{  –—†  | j                  d«      }|j	                  «       ƒ d{  –—† d	kD  r;|j                  dd¬«      ƒ d{  –—†  t        d«       t        dd«      ƒ d{  –—†  y|j                  d«      j                  }	|	j	                  «       ƒ d{  –—† d	kD  r;|	j                  dd¬«      ƒ d{  –—†  t        d «       t        dd«      ƒ d{  –—†  y	 | j                  d#«      j                  d$¬%«      j                  }|j	                  «       ƒ d{  –—† d	k(  r:| j                  d&«      j                  d$¬%«      j                  d'«      j                  }|j	                  «       ƒ d{  –—† d	kD  �r]	 |dk(  r|j                  dd¬«      j                  }n@|dk(  r|j                  dd¬«      j                  }n|j                  dd¬«      j                  }|j                  dd¬«      ƒ d{  –—†  t        dd«      ƒ d{  –—†  |r|dv r	 |j                  d(«      ƒ d{  –—†  d)D ]¤  }|j                  d*«      r| j                  |«      j                  n|j                  |«      j                  }	|	j	                  «       ƒ d{  –—† d	kD  sŒf|	j                  dd¬«      ƒ d{  –—†  t        d+|› d,�«       t        dd«      ƒ d{  –—†   y 	 t        d/«      D �]  }t        d0d«      ƒ d{  –—†  | j                  t         |«      ƒ d{  –—† }|j#                  d1«      rYt        d2|j#                  d3d4«      › d5|j#                  d6«      › d7|j#                  d8«      › d,�«       t        dd«      ƒ d{  –—†   y|j#                  d9d«      }t        d:|d;z   › d<|› �«       |d=k(  r,|d>v r(t        | «      ƒ d{  –—†  t        | d¬«      ƒ d{  –—†  t%        j&                  d?«      ƒ d{  –—†  �Œ t)        d@|› �«      ‚7 �ŒW7 �Œ<7 �Œ*7 �Œ7 �Œç# t        $ r Y �Œñw xY w7 �Œ{7 �Œj7 �ŒN7 �Œ/7 �Œ	7 �Œî7 �ŒÒ7 �Œ¡7 �Œ†7 �Œj# t        $ r}
d!|
› �}t        d"|
› �«       Y d}
~
�Œˆd}
~
ww xY w7 �ŒR7 �Œ 7 �Œ€7 �Œo7 �ŒR# t        $ r Y �Œ\w xY w7 �Œ7 �Œå7 �ŒÅ# t        $ r}
d-|
› �}t        d.|
› �«       Y d}
~
�Œád}
~
ww xY w7 �ŒË7 �Œ°7 �ŒK7 �Œ
7 Œ÷7 ŒÜ­w)AzªConfirm Hudl download format modal after the action bar Download click.

    Hudl Classic uses #downloadSettingsDlg / #beginDownloadButton (not Bootstrap .modal.in).
    )ÚdownloadFormatÚincludeBreakdownÚunknownrC   rq   NzRManage Library: visible download modal not detected yet, retrying action bar clickrH   z#downloadSettingsDlgr   rB   r^  rD   ÚsinglezSingle File per PlaylistFr§  ÚpresentationÚPresentationTzMultiple Files per Playlistr‡  r˜  rÜ  )ÚmultiplerÃ  a¿  (modal) => {
                        const boxes = Array.from(modal.querySelectorAll('input[type="checkbox"]'));
                        for (const cb of boxes) {
                            if (cb.disabled) continue;
                            const row = cb.closest('label, div, li, tr') || cb.parentElement;
                            const text = (row?.textContent || '').trim();
                            if (!text.includes('Include breakdown data')) continue;
                            if (!cb.checked) {
                                cb.click();
                                if (!cb.checked) {
                                    cb.checked = true;
                                    cb.dispatchEvent(new Event('change', { bubbles: true }));
                                }
                            }
                            return !!cb.checked;
                        }
                        return false;
                    }z3Manage Library: include breakdown checkbox checked=ú#beginDownloadButtonzAManage Library: confirmed download modal via #beginDownloadButtonrv   rt   zcinput[type="button"][value="Download"], input.submit[value="Download"], button:has-text("Download")zDManage Library: confirmed download modal via classic footer Downloadz"classic_playwright_confirm_failed:z.Manage Library: classic modal confirm failed: z.modal.in, .modal.showzChoose a Download Formatr¤  z$.modalPopup, .modal, [role='dialog']r]  a„  (modal) => {
                            for (const cb of modal.querySelectorAll('input[type="checkbox"]')) {
                                if (cb.disabled) continue;
                                const row = cb.closest('label, div, li, tr') || cb.parentElement;
                                if (!(row?.textContent || '').includes('Include breakdown data')) continue;
                                if (!cb.checked) {
                                    cb.click();
                                    if (!cb.checked) {
                                        cb.checked = true;
                                        cb.dispatchEvent(new Event('change', { bubbles: true }));
                                    }
                                }
                                return true;
                            }
                            return false;
                        })rÇ  z&input[type="button"][value="Download"]zinput.submit[value="Download"]z.footer input[value="Download"]z).modal-footer button:has-text("Download")zbutton:has-text("Download")Ú#z9Manage Library: confirmed download modal via Playwright (r.  zplaywright_confirm_failed:z1Manage Library: Playwright modal confirm failed: é   rÂ  rä   z-Manage Library: confirmed download modal via r¡  r°  z (breakdown=ÚbreakdownCheckedz, btn=ÚdownloadButtonIdrÛ  zDownload modal confirm attempt r   rÞ   Úvisible_modal_not_found)rN  é   ra  z.Could not confirm Hudl download format modal: )r¼  r^   r­  r,   r.   rR   r0   r­  r-   r  r   rj   Úlastrª  rS   r…  Ú_CONFIRM_DOWNLOAD_MODAL_JSr´   r<  rÃ  rT   )r#   r½  r¾  r;  Úlast_reasonÚclassic_modalr’  rf  Ú	begin_btnr©  rm   Úvisible_modalÚbtn_selr“  r  s                  r   Ú&_confirm_manage_library_download_modalrÕ  ›  sP  è ø€ ð "1ÐFWÑX€GØ€Kä1°$ÀÔF×FÐFÜÐbÔcÜ3°DÓ9×9Ð9Ü.¨t¸UÔC×CÐCð —L‘LÐ!7Ó8€MØ× Ñ Ó"×" QÓ&ð	Ø×(Ñ(¨yÀ$Ð(ÓG×GÐGð4	JØ (Ò*Ø#×/Ñ/Ð0JÐRWÐ/ÓX×^Ñ^‘Ø  NÒ2Ø#×/Ñ/°ÀdÐ/ÓK×QÑQ‘à#×/Ñ/Ð0MÐUZÐ/Ó[×aÑa�Ø—)‘) D°�)Ó5×5Ð5Ü˜c 3Ó'×'Ð'á  _Ð8NÑ%Nà -× 6Ñ 6ðó!÷ �ô( ÐKÈGÈ9ÐUÔVÜ! # sÓ+×+Ð+àŸ™Ð%;Ó<ˆIØ—_‘_Ó&×&¨Ò*Ø—o‘o¨d¸$�oÓ?×?Ð?ÜÐYÔZÜ! # sÓ+×+Ð+Øà(×0Ñ0Øuóç‰dð ð "×'Ñ'Ó)×)¨AÒ-Ø"×(Ñ(°¸TÐ(ÓB×BÐBÜÐ\Ô]Ü! # sÓ+×+Ð+Øð	 .ð —L‘LÐ!9Ó:×AÑAØ+ð Bó ç�eð ð × Ñ Ó"×" aÒ'ØŸ™Ð%KÓL×SÑSØ/ð Tó 
ç
‰'�.Ó
!§%¡%ð 	ð × Ñ Ó"×" QÓ&ð7	MØ (Ò*Ø#×/Ñ/Ð0JÐRWÐ/ÓX×^Ñ^‘Ø  NÒ2Ø#×/Ñ/°ÀdÐ/ÓK×QÑQ‘à#×/Ñ/Ð0MÐUZÐ/Ó[×aÑa�Ø—)‘) D°�)Ó5×5Ð5Ü˜c 3Ó'×'Ð'á  _Ð8NÑ%NðØ'×0Ñ0ðó÷ ð ó*�ð ×)Ñ)¨#Ô.ð —L‘L Ó)×/Ò/à&×.Ñ.¨wÓ7×<Ñ<ð ð
 &×+Ñ+Ó-×-°Ó1Ø&×,Ñ,°TÀÐ,ÓF×FÐFÜØSÐT[ÐS\Ð\]Ð^ôô & c¨3Ó/×/Ð/Ùñ'ô0 ˜—9ˆÜ˜$ Ó$×$Ð$Ø—}‘}Ô%?ÀÓI×IˆØ�:‰:�dÔÜØ?ÀÇ
Á
È8ÐUYÓ@ZÐ?[ð \Ø$Ÿj™jÐ);Ó<Ð=¸VÀFÇJÁJÐOaÓDbÐCcÐcdðfôô ˜c 3Ó'×'Ð'ÙØ—j‘j ¨9Ó5ˆÜÐ/°¸!±¨}¸B¸{¸mÐLÔMØÐ3Ò3¸À6Ñ8IÜ7¸Ó=×=Ð=Ü2°4ÀÔF×FÐFÜ�m‰m˜DÓ!×!Ò!ð ô" ÐEÀkÀ]ÐSÓ
TÐTðm Gùà9ùØCùð #ùàGúÜò 	Úð	úð 6ùØ'ùðùð* ,ùð 'ùØ?ùà+ùð *ùØBùà+úäò 	JØ>¸s¸eÐDˆKÜÐBÀ3À%ÐH×IÒIûð	Júð #ùð
 #ùð 6ùØ'ùðúô$ !ò Úðúð  .ùØFùð 0úäò 	MØ6°s°eÐ<ˆKÜÐEÀcÀUÐK×LÒLûð	Múð
 	%ùØIùð (ùð
 >ùØFøØ!ús®  ‚X:šU1›X:¸U4¹X:ÁU7Á(X:Á6U:Á7X:Â V  ÂU=ÂV  ÂA9V. ÄVÄV. Ä)VÄ*V. ÅVÅ	!V. Å*VÅ+(V. ÆVÆV. Æ1VÆ2V. ÇV"ÇV. ÇX:Ç.V. ÈV%ÈV. È"V(È#V. ÉV+ÉV. ÉA X:ÊWÊAX:ËWËX:Ë%A9X  ÍWÍX  Í2W!Í3
X  Í>W' ÎW$ÎW' ÎAX  Ï6W7Ï7X  Ï?X  ÐW:Ð"X  Ð8W=Ð9X  Ð>X:Ð?X  Ñ X:ÑX*Ñ X:Ñ=X-Ñ>A'X:Ó%X0Ó&AX:Ô)X3Ô*X:Ô>X6Ô?X:ÕX8ÕX:Õ4X:Õ7X:Õ:X:Õ=V  Ö 	VÖ	X:ÖVÖX:ÖV. ÖV. ÖV. ÖV. ÖV. ÖV. Ö"V. Ö%V. Ö(V. Ö+V. Ö.	WÖ7W×
X:×W×X:×X:×X  ×!X  ×$W' ×'	W4×0X  ×3W4×4X  ×:X  ×=X  Ø 	X'Ø	X"ØX:Ø"X'Ø'X:Ø-X:Ø0X:Ø3X:Ø6X:Ø8X:c           
   ƒ   ó|  ‡ ‡‡‡
‡‡K  — |j                  «       Š|xs dj                  «       }‰st        d«      ‚|st        d«      ‚t        |xs g D �cg c]&  }|sŒ|j                  «       sŒ|j                  «       ‘Œ( c}«      Št        t	        |«      «      Š
dt
        fˆ
ˆˆ ˆˆˆfd„}	 t        j                   |«       d¬«      ƒ d{  –—† S c c}w 7 Œ	# t        j                  $ r t        d	‰›d
‰
›�«       d‰‰‰
‰ddœcY S t        $ r+}	t        d|	› �«       d‰‰‰
‰dt        |	«      dœcY d}	~	S d}	~	ww xY w­w)zîMirror a single frontend clip selection onto the Hudl Manage Library page.

    Checks (selected=True) or unchecks (selected=False) the clip's checkbox so the
    open Hudl page reflects exactly what the user selects in the frontend.
    r&   r  zlabel is requiredr   c               “   ó´  •K  — d‰j                   xs dvr
d‰‰‰‰dddœS t        ‰‰‰d‰‰¬«      ƒ d {  –—† } t        | j                  d	«      «      }t	        d
‰›d‰›d‰›d‰› d|› d| j                  d«      › d| j                  d«      › �«       |rd	nd‰‰‰‰t        | j                  d«      «      | j                  d«      | j                  d«      dœS 7 Œ¡­w)Nr  r&   ÚmissFÚnot_on_manage_library_page©r�   r$   rË  r�   r5   ÚchangedrÛ  Úset_clip)r6  r5   rä   zManage Library select: season=r
  ú label=z
 selected=z ok=z	 changed=rÛ  rC  rÛ  rf  )r�   r$   rË  r�   r5   rÛ  rf  rÛ  )r   r<  r·   r´   r^   )r  rä   rj  Ú	norm_pathr#   rJ  r5   r$   s     €€€€€€r   r&  z/set_manage_library_clip_selection.<locals>._runW  s  øè ø€ ð  d§h¡h¢n°"Ñ5à Ø"Ø &ØØ$Ø Ø6ñð ô -Ø�&˜) Z¸DÈ8ô
÷ 
ˆô �&—*‘*˜TÓ"Ó#ˆÜØ,¨V¨J°f¸Y¸Mð JØ�H˜J x j°°R°Dð 9Ø—z‘z )Ó,Ð-¨X°f·j±jÀÓ6JÐ5KðMô	
ñ !‘d fØØ"ØØ Ü˜FŸJ™J yÓ1Ó2Ø—z‘z )Ó,Ø—j‘j Ó*ñ	
ð 		
ð
ús   ƒ1C´CµB"Cg      $@rq   Nz4Manage Library select timed out (non-fatal): season=rÝ  rF   F)r�   r$   rË  r�   r5   rÛ  z)Manage Library select error (non-fatal): r€   rÚ  )r*   rT   r5  r2  r-  r³   r<  rR   r?  r^   r0   rd   )r#   r$   rË  rú  r�   r5   r6  r4  r&  rm   rj  rÞ  rJ  s   ``   `    @@@r   Ú!set_manage_library_clip_selectionrß  >  sS  ýè ø€ ð ×ÑÓ!€FØ’+˜2×$Ñ$Ó&€JÙÜÐ3Ó4Ð4ÙÜÐ,Ó-Ð-ä&Ø!šZ R˜ZÓ<™Z�qªA°!·'±'µ)ˆ�‰�˜ZÑ<ó€Iô "Ô"5°jÓ"AÓB€Dð%
œ÷ %
ò %
ðR
Ü×%Ñ%¡d£f°dÔ;×;Ð;ùò] 	=ð\ <ùÜ×Ñò 
ÜðØ�Z˜w t hð0ô	
ð
  ØØ"ØØ Øñ
ò 	
ô ò 

ÜÐ9¸#¸Ð?Ô@àØØ"ØØ ØÜ˜#“hñ
õ 	
ûð

üsl   ˆAD<ÁCÁCÁ-CÁ?,D<Â,C ÃCÃC ÃD<ÃC Ã-D9ÄD<ÄD9Ä D4Ä.D9Ä/D<Ä4D9Ä9D<uE  
() => {
    const isVisible = (el) => {
        if (!el) return false;
        const rect = el.getBoundingClientRect();
        const style = window.getComputedStyle(el);
        return (
            rect.width > 4 &&
            rect.height > 4 &&
            style.display !== 'none' &&
            style.visibility !== 'hidden' &&
            parseFloat(style.opacity || '1') > 0.05
        );
    };
    const errRe = /try that download again|uh oh,? something didn.?t work/i;
    const okRe = /email you a link|ready to download/i;
    const nodes = Array.from(document.querySelectorAll(
        '.manage-library-notification-wrapper, [class*="manage-library-notification"], '
        + '.flash, .flashMessage, .notification, .alert, .toast, .banner, [role="alert"], '
        + '[class*="flash"], [class*="notif"], [class*="banner"], '
        + 'div, section, header, aside'
    ));
    let sawError = false;
    for (const node of nodes) {
        if (!isVisible(node)) continue;
        const text = (node.innerText || node.textContent || '').replace(/\s+/g, ' ').trim();
        if (!text || text.length > 220) continue;
        // Prefer success â€” a green banner means this attempt worked.
        if (okRe.test(text)) return 'success';
        if (errRe.test(text)) sawError = true;
    }
    return sawError ? 'error' : 'pending';
}
ay  
() => {
    const isVisible = (el) => {
        if (!el) return false;
        const rect = el.getBoundingClientRect();
        const style = window.getComputedStyle(el);
        return (
            rect.width > 2 &&
            rect.height > 2 &&
            style.display !== 'none' &&
            style.visibility !== 'hidden' &&
            parseFloat(style.opacity || '1') > 0.05
        );
    };
    const clickEl = (el) => {
        el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }));
        if (typeof el.click === 'function') el.click();
    };
    const errRe = /(try that download again|something did\S* ?work)/i;
    const nodes = Array.from(document.querySelectorAll('div, section, header, aside'));
    let banner = null;
    for (const node of nodes) {
        if (!isVisible(node)) continue;
        const text = (node.innerText || '').replace(/\s+/g, ' ').trim();
        if (errRe.test(text) && text.length < 200) banner = node;
    }
    if (!banner) return { ok: false, reason: 'error_banner_not_found' };
    const candidates = Array.from(
        banner.querySelectorAll('button, a, span, i, svg, [role="button"], [class*="close"], [class*="dismiss"]')
    ).filter(isVisible);
    let best = null;
    let bestRight = -Infinity;
    for (const el of candidates) {
        const txt = (el.textContent || '').trim();
        const cls = String(el.className || '').toLowerCase();
        const isClose =
            /close|dismiss/.test(cls) || txt === '\u00d7' || txt === '\u2715' || /^x$/i.test(txt);
        if (!isClose && txt.length > 2) continue;
        const right = el.getBoundingClientRect().right;
        if (right > bestRight) {
            bestRight = right;
            best = el;
        }
    }
    if (best) {
        clickEl(best);
        return { ok: true };
    }
    return { ok: false, reason: 'error_close_not_found' };
}
c              ƒ   ó4  K  — ddl }|j                  «       |z   }|j                  «       |k  rU	 | j                  t        «      ƒ d{  –—† }|dv r|S t        j                  d«      ƒ d{  –—†  |j                  «       |k  rŒUy7 Œ<# t        $ r d}Y ŒGw xY w7 Œ,­w)zNPoll for Hudl's post-download banner. Returns 'success' | 'error' | 'unknown'.r   NÚpending)Úsuccessr€   ra  rÂ  )ÚtimeÚ	monotonicrj   Ú_DOWNLOAD_OUTCOME_JSr0   r<  rÃ  )r#   rF   Ú_tÚdeadlineÚoutcomes        r   Ú_wait_for_download_outcomeré  û  s”   è ø€ ãà�|‰|‹~ Ñ'€HØ
�,‰,‹.˜8Ò
#ð	 Ø ŸM™MÔ*>Ó?×?ˆGð Ð*Ñ*ØˆNÜ�m‰m˜DÓ!×!Ð!ð �,‰,‹.˜8Ó
#ð ð @ùÜò 	 ØŠGð	 úð 	"úsL   ‚+B®B ÁBÁB ÁBÁ)BÁ*BÂBÂB ÂBÂBÂBÂBc              ƒ   óâ   K  — 	 | j                  t        «      ƒ d{  –—† }|j                  d«      r)t        d«       t	        j
                  d«      ƒ d{  –—†  yy7 Œ?7 Œ# t        $ r Y yw xY w­w)zDBest-effort close of Hudl's red 'something didn't work' error strip.Nrä   z3Manage Library: dismissed red download error bannerrÜ  )rj   Ú_DISMISS_DOWNLOAD_ERROR_JSr´   r^   r<  rÃ  r0   )r#   r  s     r   Ú&_dismiss_manage_library_download_errorrì    sf   è ø€ ðØ—}‘}Ô%?Ó@×@ˆØ�:‰:�dÔÜÐGÔHÜ—-‘- Ó$×$Ñ$ð ð Aøð %ùÜò ÙðüsJ   ‚A/„A  œA�8A  ÁAÁA  ÁA/ÁA  ÁA  Á 	A,Á)A/Á+A,Á,A/r©   c              ƒ   ó4  ‡ ‡‡‡‡‡	K  — |j                  «       Š|xs g Š	‰st        d«      ‚‰	st        d«      ‚dt        fˆˆˆ ˆˆ	ˆfd„}	 t        j                   |«       d¬«      ƒ d{  –—† S 7 Œ# t        j
                  $ r}t        d«      |‚d}~ww xY w­w)	zGNavigate Hudl Manage Library, select clips, and trigger email download.r  zitems is requiredr   c               “   óü  •K  — t        ‰‰«      ƒ d {  –—† } t        ‰«      ƒ d {  –—†  t        ‰«      ƒ d {  –—†  t        ‰«      ƒ d {  –—†  t	        ‰«      ƒ d {  –—†  t        ‰d‰› d�«      ƒ d {  –—†  t        ‰‰g d¬«      ƒ d {  –—†  t        j                  d«      ƒ d {  –—†  g }d }‰D �]|  }t        |j                  d«      xs g D �cg c]#  }|j                  «       sŒ|j                  «       ‘Œ% c}«      }t        t        |j                  d«      xs dj                  «       «      «      }|sŒ‡t        d	‰›d
|›d|›�«       t        ‰d‰› ddj                  g |¢|‘«      › �«      ƒ d {  –—†  ||k7  rt!        ‰‰|«      ƒ d {  –—†  |}t#        ‰‰||«      ƒ d {  –—† }|s6t        d«       t!        ‰‰|«      ƒ d {  –—†  t%        ‰‰||«      ƒ d {  –—† }|s$t'        d|› ddj                  |«      xs d› �«      ‚|j)                  |«       t+        dd«      ƒ d {  –—†  �Œ |st'        d«      ‚t-        ‰d¬«      ƒ d {  –—† st'        d«      ‚t/        ‰«      ƒ d {  –—† st'        d«      ‚d}‰}	‰g}
‰r|
j)                  d«       n|
j)                  ‰«       t1        |
d d «      D ]·  \  }}|}	t3        ‰‰|¬«      ƒ d {  –—†  t5        ‰d¬«      ƒ d {  –—† }|dk(  r n€|d k7  r(t5        ‰d!¬«      ƒ d {  –—† }|d k(  rd }n|dk(  rd} nU nSt        d"|d#z   › d$|› d%�«       t        ‰«      ƒ d {  –—†  t+        d&d'«      ƒ d {  –—†  t/        ‰«      ƒ d {  –—† rŒ· n |d k(  rt'        d(«      ‚t        ‰«      ƒ d {  –—†  t	        ‰«      ƒ d {  –—†  d)| ‰|‰|	d*d+œS 7 �Œ¢7 �Œ’7 �Œ‚7 �Œr7 �Œb7 �ŒM7 �Œ97 �Œc c}w 7 �ŒY7 �ŒB7 �Œ-7 �Œ7 �Œû7 �Œ³7 �Œ‘7 �Œv7 �Œ7 �Œ7 Œå7 Œ¬7 Œš7 Œ‰7 Œd7 ŒS­w),NzDownload: opening z (year only)Tr  rÝ  rú  r�   r&   z/Manage Library download: selecting clip season=r
  rÝ  zDownload select: r  z;Manage Library download: quick select missed, re-navigatingzCould not select clip 'z
' at path rK   z(season root)r  g
×£p=
Ç?z#No clips were selected for downloadrC   rq   zOClip selection did not activate the Hudl action bar (no 'Item selected' banner)z5Could not click Download in Manage Library action barrÂ  Fr   )r½  r¾  g       @râ  r€   r\   z8Manage Library: Hudl reported a download error (attempt r   z/2, include_breakdown=z); retryingrt   rr   zSHudl rejected the download ("something didn't work"). Please try again in a moment.rä   z<We'll email you a link when your video is ready to download.)r�   r$   rË  r©   Úformatr¾  rƒ   )r'  r½  rì  r¹  r´  r  rÞ  r<  rÃ  r5  r´   r*   r2  r-  r^   r�  rH  r£  r¨  rT   r¸   r   rš  r­  rï   rÕ  ré  )r  Úselected_labelsÚ	last_pathr»   r4  rú  r6  rä   rè  Úused_breakdownÚattempt_flagsr“  Úbreakdown_flagÚlater½  r¾  r#   rJ  r5   r$   s                 €€€€€€r   r&  z+download_manage_library_items.<locals>._run&  s�  øè ø€ Ü!<¸TÀ7Ó!K×KÐÜ+¨DÓ1×1Ð1ô 5°TÓ:×:Ð:Ü5°dÓ;×;Ð;ô
 4°DÓ9×9Ð9ä%ØÐ& v h¨lÐ;ó
÷ 	
ð 	
ô & d¨F°BÈÔM×MÐMÜ�m‰m˜CÓ × Ð à%'ˆØ&*ˆ	ÜˆDÜ)Ø%)§X¡X¨fÓ%5Ò%;¸Ð%;ÓJÑ%;˜qÀÇÁÅ	�—‘•Ð%;ÑJóˆDô 0Ü# T§X¡X¨gÓ%6Ò%<¸"×$CÑ$CÓ$EÓFóˆJñ ØäðØ ˜ 6¨$¨°¸¸ðHôô *ØØ# F 8¨5°·±Ð>QÀÐ>QÀjÐ>QÓ1RÐ0SÐTó÷ ð ð �yÒ Ü3°D¸&À$ÓG×GÐGØ �	ä8¸¸vÀtÈZÓX×XˆBÙÜÐSÔTÜ3°D¸&À$ÓG×GÐGÜ6°t¸VÀTÈ:ÓV×V�ÙÜ Ø-¨j¨\¸ÀCÇHÁHÈTÃNÒDeÐVeÐCfÐgóð ð ×"Ñ" :Ô.Ü˜d DÓ)×)Ò)ðE ñH ÜÐBÓCÐCä;¸DÈ%ÔP×PÐPÜØaóð ô ;¸4Ó@×@Ð@ÜÐTÓUÐUð
 ˆØ*ˆØ*Ð+ˆÙØ× Ñ  Õ'à× Ñ Ð!2Ô3ä'0°¸rÀÐ1BÖ'CÑ#ˆG�^Ø+ˆNÜ8ØØ /Ø"0ô÷ ð ô
 7°tÀSÔI×IˆGØ˜)Ò#ÙØ˜'Ò!ô 8¸ÀcÔJ×J�Ø˜7’?Ø%‘GØ˜YÒ&Ø'�GÙáäðØ# a™K˜=Ð(>¸~Ð>NÈkð[ôô 9¸Ó>×>Ð>Ü˜c 3Ó'×'Ð'Ü>¸tÓD×DÑDÙð; (Dð> �gÒÜð0óð ô 6°dÓ;×;Ð;ô 4°DÓ9×9Ð9ð Ø'Ø"Ø$Ø%Ø!/ØUñ
ð 	
ðw LùØ1ùð 	;ùØ;ùð
 	:ùð	
ùð 	NùØ ûò Kðùð Hùð Yùð HùØVùð *ùð
 Qùð
 Aùð ùð
 Jùð Køð ?øØ'øØDøð 	<øð 	:úsÄ  ƒO<“N5”O<¦N8§O<¹N;ºO<ÁN>ÁO<ÁOÁ O<Á7OÁ8O<ÂOÂO<Â+O
Â,*O<ÃO
Ã,O
Ã>A;O<Å9OÅ:O<ÆOÆO<Æ+OÆ,!O<ÇOÇO<Ç#OÇ$A
O<È.O!È/$O<ÉO$ÉO<É1O'É2A"O<ËO*ËO<Ë)O-Ë* O<Ì
O0Ì:O<ÍO2ÍO<ÍO4ÍO<Í,O6Í-O<Í2!O<ÎO8ÎO<Î&O:Î'O<Î8O<Î;O<Î>O<ÏO<ÏO<ÏO<Ï
O<ÏO<ÏO<ÏO<ÏO<ÏO<Ï!O<Ï$O<Ï'O<Ï*O<Ï-O<Ï0O<Ï2O<Ï4O<Ï6O<Ï8O<Ï:O<g      ^@rq   Nz/Timed out downloading from Hudl Manage Library.r)  )
r#   r$   rË  r©   r½  r¾  r&  rm   rJ  r5   s
   ``  ``  @@r   Údownload_manage_library_itemsrö    sš   ýè ø€ ð ×ÑÓ!€FØŠ{˜€HÙÜÐ3Ó4Ð4ÙÜÐ,Ó-Ð-ðD
œ÷ D
ò D
ðLWÜ×%Ñ%¡d£f°eÔ<×<Ð<Ð<ùÜ×Ñò WÜÐLÓMÐSVÐVûðWüs<   ˆABÁA1 Á*A/Á+A1 Á.BÁ/A1 Á1BÂBÂBÂBc              ƒ   óÄ   ‡ ‡K  — dt         fˆ ˆfd„}	 t        j                   |«       d¬«      ƒ d{  –—† S 7 Œ# t        j                  $ r}t        d«      |‚d}~ww xY w­w)zX
    Navigate to Hudl Classic Manage Library and return storage + season tree data.
    r   c               “   óØ  •K  — t        ‰‰«      ƒ d {  –—† } d| › �‰j                  v r‰j                  nt        ‰| «      ƒ d {  –—† }t        ‰«      ƒ d {  –—† }|j	                  d«      xs g }|s;d}	 ‰j                  d«      j                  «       ƒ d {  –—† d d }t        d|›�«       d|| |j	                  d«      xs i |t        |«      d	œS 7 Œ¿7 Œ�7 Œ7 Œ@# t        $ r Y ŒFw xY w­w)
Nr  rÉ  r&   r  iô  z7Manage Library parse returned 0 seasons. Body preview: rä   r!  )r�   r   r$   r!  rÉ  Úseasons_count)
r'  r   rº  r(  r´   r,   r  r0   r^   r¹   )r  r   r-  rÉ  Úbody_previewr#   r$   s        €€r   r&  z get_manage_library.<locals>._run¶  s
  øè ø€ Ü!<¸TÀ7Ó!K×KÐØ,Ð-=Ð,>Ð?À4Ç8Á8ÑKˆd�hŠhÔWrÐswð  zJó  XK÷  RKˆä1°$Ó7×7ˆØ—*‘*˜YÓ'Ò-¨2ˆáØˆLðØ&*§l¡l°6Ó&:×&EÑ&EÓ&G× GÈÈ#ÐN�ô ÐKÈLÐK[Ð\Ô]ð ØØ'Ø—z‘z )Ó,Ò2°ØÜ  ›\ñ
ð 	
ð Løð RKøà7øð !HùÜò Ùðüsi   ƒC*“C”0C*ÁCÁC*ÁCÁC*Á6"C ÂCÂC Â 4C*ÃC*ÃC*ÃC Ã	C'Ã$C*Ã&C'Ã'C*r'  rq   NzHTimed out loading Hudl Manage Library. Try again or reduce library size.)r³   r<  rR   r?  )r#   r$   r&  rm   s   ``  r   Úget_manage_libraryrû  ²  s[   ùè ø€ ð
œö 
ð0Ü×%Ñ%¡d£f°dÔ;×;Ð;Ð;ùÜ×Ñò ÜØVó
àð	ûðüs1   „A “9 ²7³9 ¶A ·9 ¹AÁAÁAÁA rÉ   )rK  )NF)Úvideo_and_statsF)NN)rÛ   )r&   NN)rØ  )Nr&   )rB  NT)Nr&   Nr&   )r³  ræ   )Nr&   N)rC   )rÆ  T)Nr&   Nr&   T)g      (@)Nr&   NrÆ  T)�r<  r   Úplaywright.async_apir   rç   r   r;  Úapp.automation.helpersr   Úapp.automation.tasks.popupr   Úapp.automation.selectorsr   r   r	   r
   r   r   r   r   r   r   r   rd   r·   r   r"   r8   rW   ra   rh   r²   r³   rn   rµ   r{   rý   re   r0  r  r  r  r  r	  r  r¶   r  r  r2  r@  rF  rY  rv  r}  rŒ  r•  r˜  r£  rª  r­  r±  r¶  rÃ  rÉ  rÕ  r¬  r  r+  r$  r%  r7  rO  r_  rb  rq  r{  r}  r�  rŒ  r³  rº  r½  rÇ  r(  rÏ  rÑ  rß  rä  rà  rô  rö  rù  rü  r�  r  r  rÞ  r  r'  r*  r-  r2  r5  r:  r<  r@  rD  rI  rL  r   r\  rp  rr  ru  rw  r}  r~  r  Ú__annotations__r  r!  rH  r–  rš  r«  rœ  r£  r¨  r­  r»  rÏ  r·  r²  r´  r¹  r¼  rÕ  rß  rå  rë  ré  rì  rö  rû  rÊ   r   r   Ú<module>r     s  ðÜ Û 	Ý %Ý 7Ý .Ý 8÷÷ ÷ ñ ðT˜#ð T $ó Tð
-˜s T™zð -¨c°D©jó -ñ¨ð ¸¸d¹
ð ÈcÐTXÉjó ñ<# Tð #°C¸$±Jð #È#ó #ñL Tð °C¸$±Jð È#ó ð( #¨¡*ð °ó ð*u¨$ð u°4¸±:ó uñp¨$ð ¸Cð ÈDó ñ4V 4ð V°#¸±*ð VÈó Vðr
 #˜Ÿ
™
Ø6Ø‡M�MóÐ ð '˜BŸJ™JÐ'<¸b¿m¹mÓLÐ Ø$&Ð !àEð ð
Øðàðð ðð 
ó	ð$°ð ¸ó ð(!¨tð !¸À¹ó !ð$+¨ð +°#ó +ñ,A^¨ð A^¸¸d¹
ð A^Èdó A^ðH+
 Tð +
°cð +
¸dó +
ðZ6 4ð 6¨Dó 6ð.K˜Dð K¨só Kð^/ Tð /¸ð /Àó /ðd Dð °sð Èð ÐPTó ð(¨$ð ¸Sð ÀTó ð<. Tð .¸ð .Èð .ÐQUó .ðd	 dð 	¨tó 	ñ. Tð .¸ð .ÀsÈTÁzð .Ðbfð .Ðswó .ñb#T Dð #T°sð #TÀCð #TÐeið #TÐvzó #TðL9 tð 9¸#ð 9À$ó 9ð-˜Sð - Só -ð
˜sð  só ð"˜3ð " 3ó "ð0 ð ¨ð °ó ñ.=˜cð =¨C°$©Jð =È#ÐPTÉ*ð =Ð`có =ð4E¨Dð E¸4ó EðP¨ð ¸ð ÀÀdÁ
ó ñ6J ð J¨#ð J¸#À¹*ó JñZ ¨ð  °ð  À#ð  ÐRVó  ñ.5 ð 5°ð 5¸sÀT¹zó 5ðp	 3ð 	¨3ó 	ðs Tð s¨cð s°dó sðp #ØñWØ
ðWà�t‘ðWð ˆT�zðWð 
ó	Wðt Tð ¸ð Àó ñ8-°ð -ÈÈdÉ
ð -Ð^aó -ð`>¨ð >°sð >¸tó >ðB- 3ð -¨3°©:ó -ð #ð ¨#ó ð|¨ð |°ó |ðB ØØñ	GØ
ðGàðGð �D‰jðGð �4‰Zð	Gð
 
óGðT ¨Dð  ¸3ð  À3ó  ðF¨dð °tó ñ4¨Dð ¸cð È4ó ð"F.¨4ð F.°Dó F.ðRMÐ ð`D tð D¸3ð DÀ4ó DðSÐ ðl@¨ð @¸Sð @ÀTó @ðFG¨Tð G¸#ð GÀ$ó GðTH°ð HÀSð HÈTó HðVF¨dð FÀ#ð FÈ$ó Fñ5¨Dð 5Àð 5ÐQTð 5Ð^bó 5ð$9°ð 9ÀCð 9ÈDó 9ðx GKò (" Tð ("¸¸t¹ð ("Ètó ("ðV¨$ð °4ó ðRÐ ðp %)ð='ð
 !ò='Ø
ð='àð='ð �c‘˜TÑ!ð='ð
 ð='ð 
ó='ðB 05ò#2Ø
ð#2Øð#2Ø(,ð#2à	ó#2ðL"¨Dð "¸3À¹:ð "È#ó "ðL ACñGVØ
ðGVØ˜t™ðGVØ:=ðGVà	óGVðT@˜sð @ só @ð #ð ¨#ó ð.Y¨¨S©	ð Y°d¸3±ió YðXÐ ð~ Ø!ØñAØ
ðAàðAð �c‘ðAð ð	Að
 �d‘
ðAð ðAð 
óAð,	Ø
ð	Ø!ð	Ø04°S±	ð	à	ó	ð(Ø
ð(Ø!ð(Ø04°S±	ð(à	ó(ðIÐ ð\ ØØ$(Øñ9Ø
ð9à�4‰Zð9ð ð9ð �c‘˜TÑ!ð	9ð
 ð9ð 
ó9ðz 89ñ1Ø
ð1Ø!ð1Ø14ð1à	ó1ðj PQñ
Ø
ð
Ø!ð
Ø04°S±	ð
ØILð
à	ó
ðwØ
ðwØ!ðwØ+/°©9ðwØ>Aðwà	ówðt	7¨ð 	7¸Cð 	7ÀDó 	7ð1 Dð 1°ð 1¸ó 1ðhb¨Dð b¸ð bÀó bðJJÐ ðZKØ
ðKØ!ðKØ04°S±	ðKà	óKð  "¨R¸Ñ<€
ˆDÓ <óð$˜C $™Jð $°ð $¸4À¹9ð $Èó $ðJIØ
ðJIØ!ðJIØ04°S±	ðJIà	óJIð^ ØØ$(ñ	GØ
ðGà�4‰ZðGð ðGð �c‘˜TÑ!ð	Gð
 
óGñT°tð Àeð ÐX\ó ð 6Ð ðt DHñ+Ø
ð+Ø!ð+Ø04°S±	¸DÑ0@ó+ð\)Ø
ð)Ø!ð)Ø04°S±	ð)ØGJð)à	ó)ðX5Ø
ð5Ø!ð5Ø04°S±	ð5ØGJð5à	ó5ðp%°dð %¸tó %ðP@#Ð ðFjÐ ð`lÐ ðbiÐ ðX"G°dð "G¸tó "GðJ1V¸ð 1VÀó 1Vñh	°ð 	Àð 	ÐRVó 	ð NRñ`UØ
ð`UØ!$ð`UØFJð`Uà	ó`UðJ ØØ!ØØñ[
Ø
ð[
à�4‰Zð[
ð ð[
ð ˆs‰)�dÑ
ð	[
ð
 ð[
ð ð[
ð 
ó[
ðF!Ð ðJ2Ð ñj¨4ð ¸%ð È3ó ð °tð Àó ð ØØ#Ø%Ø"ñYWØ
ðYWà�4‰ZðYWð ðYWð �‰:˜Ñð	YWð
 ðYWð ðYWð 
óYWñx! 4ð !°#¸±*ð !Èô !r   