Skip to content

feat: implement embedded video player with ultra-large modal#770

Open
omsherikar wants to merge 6 commits into
alphaonelabs:mainfrom
omsherikar:feature/embedded-video-player-620-clean
Open

feat: implement embedded video player with ultra-large modal#770
omsherikar wants to merge 6 commits into
alphaonelabs:mainfrom
omsherikar:feature/embedded-video-player-620-clean

Conversation

@omsherikar
Copy link
Copy Markdown
Contributor

@omsherikar omsherikar commented Oct 23, 2025

Related issues

Fixes #620

Checklist

  • Did you run the pre-commit? (If not, your PR will most likely not pass — please ensure it passes pre-commit)
  • Did you test the change? (Ensure you didn’t just prompt the AI and blindly commit — test the code and confirm it works)
  • Added screenshots to the PR description (if applicable)

Summary by CodeRabbit

  • New Features

    • Videos now open in an accessible modal on the videos list page with embedded playback (YouTube/Vimeo), fallback opening in a new tab for unsupported URLs, and prevents full-page navigation.
    • Modal includes ARIA updates, focus trapping, restores focus on close, supports Escape key and outside-click to dismiss, and injects contextual titles.
  • Style

    • Updated video card and modal visuals with responsive layout, hover effects, shadows, gradients, rounded corners, and show/hide animations.

✏️ Tip: You can customize this high-level summary in your review settings.

- Add beautiful video modal with YouTube/Vimeo embed support
- Implement ultra-large responsive modal (95vw width, 70-85vh height)
- Add smooth animations and loading states with gradient backgrounds
- Include full accessibility support with ARIA labels and focus management
- Use semantic HTML5 elements (dialog, header, section) for better structure
- Follow Tailwind CSS best practices with @layer components
- Add keyboard navigation and screen reader support
- Implement mobile-first responsive design with dark mode support
- Replace YouTube redirects with embedded video player
- Maintain CONTRIBUTING.md compliance throughout

Closes alphaonelabs#620
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @omsherikar, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the user experience for viewing educational videos by integrating an embedded video player within an ultra-large, responsive modal. Instead of navigating away from the page, users can now watch videos directly, benefiting from a seamless, modern interface and improved accessibility features. The changes include comprehensive styling, dynamic video embedding logic for popular platforms like YouTube and Vimeo, and thoughtful keyboard and screen reader support.

Highlights

  • Embedded Video Player: Videos now play directly within a large, responsive modal on the page, eliminating the need to open new tabs for viewing.
  • Modern Modal Styling: A new, aesthetically pleasing modal has been introduced, featuring smooth transitions, gradient backgrounds, and responsive design, all styled using Tailwind CSS.
  • YouTube and Vimeo Integration: The embedded player automatically detects and correctly embeds videos from both YouTube and Vimeo, providing a consistent playback experience.
  • Accessibility Enhancements: The modal includes robust accessibility features such as keyboard navigation (Escape key to close, Tab trapping), ARIA attributes, and focus management for an inclusive user experience.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions github-actions Bot added the f1 label Oct 23, 2025
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Oct 23, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

A single template file is modified to add an inline Tailwind-styled video modal and associated JS. Video links now open an in-page dialog that embeds YouTube/Vimeo iframes (autoplay), manages ARIA and focus, supports Escape/outside-click dismissal, and includes responsive styling and animations.

Changes

Cohort / File(s) Change Summary
Video Player Modal Implementation
web/templates/videos/list.html
Adds dialog/modal markup and styling (Tailwind classes), replaces direct video links with modal-triggering links, introduces openVideoModal(videoUrl, videoTitle, event) JS to parse YouTube/Vimeo IDs, construct autoplay embed URLs, set/clear iframe src, manage ARIA attributes and focus trapping, handle Escape/Tab/outside-click to close, restore focus on close, and responsive/modal animations.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Page as Videos List Page (HTML)
    participant JS as Modal Script
    participant Modal as Dialog UI
    participant IFrame as Embed IFrame

    User->>Page: Click video thumbnail/title (modal-trigger link)
    Page->>JS: call openVideoModal(videoUrl, videoTitle, event)
    activate JS
    JS->>JS: parse URL -> identify YouTube or Vimeo + media ID
    JS->>JS: build embed URL with autoplay params
    JS->>Modal: set title, ARIA attributes, show/animate dialog
    JS->>IFrame: set iframe.src to embed URL
    JS->>JS: trap focus inside Modal, store trigger element
    deactivate JS

    rect rgb(230,245,255)
    Note over User,Modal: Modal active — responds to Escape, outside-click, close button, Tab trap
    end

    User->>Modal: Request close (Escape / outside click / close button)
    activate JS
    JS->>Modal: animate close, remove ARIA attributes
    JS->>IFrame: clear iframe.src
    JS->>JS: restore focus to trigger and page scroll
    deactivate JS
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Inspect openVideoModal URL parsing for edge cases (non-YouTube/Vimeo links).
  • Verify focus-trap and restore logic across browsers and screen readers.
  • Confirm iframe src is reliably cleared and autoplay behavior is acceptable.
  • Review responsive styling and potential CSS collisions with existing Tailwind utilities.

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: implementing an embedded video player that opens in a modal.
Linked Issues check ✅ Passed All primary objectives from issue #620 are met: prevents external YouTube redirection, implements embedded video player using iframe, and provides lightbox-style modal.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the embedded video player modal feature; no out-of-scope changes detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces an embedded video player within a modal, which is a great enhancement for user experience. The implementation correctly uses a <dialog> element for accessibility and includes features like keyboard navigation and focus trapping. However, there are several areas for improvement. The CSS is currently embedded within the HTML, which should be moved to a separate file for better maintainability. The JavaScript for animations is a bit brittle, relying on direct style manipulation and setTimeout instead of more robust class-toggling and event-based approaches. There's also a flaw in the focus restoration logic upon closing the modal, which impacts accessibility. Additionally, I've pointed out a redundant code block and suggested modernizing the anchor tags for opening the modal. My review includes specific suggestions to address these points.

Comment thread web/templates/videos/list.html Outdated
Comment thread web/templates/videos/list.html Outdated
Comment thread web/templates/videos/list.html
Comment on lines +219 to +221
<a href="javascript:void(0)"
onclick="openVideoModal('{{ video.video_url }}', '{{ video.title|escapejs }}')"
class="video-link absolute inset-0 flex items-center justify-center bg-black bg-opacity-25 hover:bg-opacity-0 transition-opacity">
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using href="javascript:void(0)" is an outdated practice. For better accessibility and progressive enhancement, it's recommended to provide a meaningful fallback URL in the href attribute. You could use the video URL itself as the href and then prevent the default link behavior in your onclick handler. This ensures that if JavaScript is disabled or fails to load, the link will still take the user to the video.

                      <a href="{{ video.video_url }}"
                         onclick="event.preventDefault(); openVideoModal('{{ video.video_url }}', '{{ video.title|escapejs }}')"
                         class="video-link absolute inset-0 flex items-center justify-center bg-black bg-opacity-25 hover:bg-opacity-0 transition-opacity">

Comment on lines +245 to +247
<a href="javascript:void(0)"
onclick="openVideoModal('{{ video.video_url }}', '{{ video.title|escapejs }}')"
class="video-link hover:text-orange-500 transition-colors cursor-pointer">{{ video.title }}</a>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using href="javascript:void(0)" is an outdated practice. For better accessibility and progressive enhancement, it's recommended to provide a meaningful fallback URL in the href attribute. You could use the video URL itself as the href and then prevent the default link behavior in your onclick handler. This ensures that if JavaScript is disabled or fails to load, the link will still take the user to the video.

                    <a href="{{ video.video_url }}"
                       onclick="event.preventDefault(); openVideoModal('{{ video.video_url }}', '{{ video.title|escapejs }}')"
                       class="video-link hover:text-orange-500 transition-colors cursor-pointer">{{ video.title }}</a>

Comment thread web/templates/videos/list.html
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
web/templates/videos/list.html (2)

8-144: Move custom CSS to dedicated stylesheet or use Tailwind extensions; don't embed styling in templates.

The coding guidelines explicitly require "Use Tailwind CSS classes only in templates (no custom CSS or inline styles)." This 136-line <style> block violates that requirement. Custom @layer components, @keyframes, box-shadow definitions, and media queries belong in a dedicated CSS file where they can be:

  • Properly formatted with djlint
  • Reused across templates
  • Easier to maintain and test
  • Bundled/minified in production

Extract all custom styles to a new stylesheet (e.g., web/static/css/video-modal.css), then import it in the template's {% block extra_head %} or in your main CSS pipeline.

Here's the recommended structure:

/* web/static/css/video-modal.css */

@layer components {
  .video-modal {
    @apply hidden fixed inset-0 z-50 bg-black bg-opacity-90 backdrop-blur-sm opacity-0 transition-all duration-300 ease-out;
  }

  .video-modal.show {
    @apply flex items-center justify-center opacity-100;
  }

  /* ... rest of component styles ... */
}

@keyframes fadeIn {
  from {
    opacity: 0;
    transform: translateY(-10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.form-section {
  display: block;
  animation: fadeIn 0.3s;
}

@media (max-width: 768px) {
  /* ... responsive styles ... */
}

Then in the template:

{% block extra_head %}
  <link rel="stylesheet" href="{% static 'css/video-modal.css' %}">
{% endblock %}

This also allows you to leverage Tailwind's @apply and responsive utilities properly.


219-247: Use semantic link markup or <button> elements instead of href="javascript:void(0)"; improves accessibility and keyboard navigation.

Using href="javascript:void(0)" with onclick handlers breaks keyboard semantics, screen reader announcements, and tab order predictability. The video title link (line 245) and thumbnail play buttons (lines 219, 231) should be actual <button> elements or proper links with href attributes that defer to JavaScript event handlers via proper listeners.

Refactor to use semantic <button> elements:

-                     <a href="javascript:void(0)"
+                     <button type="button"
-                         onclick="openVideoModal('{{ video.video_url }}', '{{ video.title|escapejs }}')"
+                         data-video-url="{{ video.video_url|escapejs }}"
+                         data-video-title="{{ video.title|escapejs }}"
+                         aria-label="Play video: {{ video.title }}"
                         class="video-link absolute inset-0 flex items-center justify-center bg-black bg-opacity-25 hover:bg-opacity-0 transition-opacity">
                        <i class="fas fa-play text-white text-4xl"></i>
-                     </a>
+                     </button>

And for the title link:

-                   <a href="javascript:void(0)"
+                   <button type="button"
-                      onclick="openVideoModal('{{ video.video_url }}', '{{ video.title|escapejs }}')"
+                      data-video-url="{{ video.video_url|escapejs }}"
+                      data-video-title="{{ video.title|escapejs }}"
                       class="video-link hover:text-orange-500 transition-colors cursor-pointer">{{ video.title }}</button>

Then add a single event delegation listener in the JavaScript:

document.addEventListener('click', function(e) {
  const btn = e.target.closest('button.video-link');
  if (btn) {
    openVideoModal(btn.dataset.videoUrl, btn.dataset.videoTitle);
  }
});

This approach:

  • Maintains proper keyboard focus and tab order
  • Works reliably with screen readers
  • Avoids the javascript: protocol antipattern
  • Centralizes event handling
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ad91f49 and 415b543.

📒 Files selected for processing (1)
  • web/templates/videos/list.html (4 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.html

📄 CodeRabbit inference engine (.cursorrules)

**/*.html: Always use Tailwind CSS utility classes for styling HTML elements
Never use inline styles on elements (no style="...")
Use the project color scheme via Tailwind classes: teal-300 (primary), gray-600 (secondary), green-600 (success), yellow-600 (warning), red-600 (danger), with dark: variants
Containers should use: container mx-auto px-4
Cards should use: bg-white dark:bg-gray-800 rounded-lg shadow-lg
Form inputs should use: w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500
Form labels should use: block text-sm font-medium text-gray-700 dark:text-gray-300
Primary buttons should use: bg-teal-300 hover:bg-teal-400 text-white px-6 py-2 rounded-lg transition duration-200
Secondary buttons should use: bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300
Headings use Tailwind typography: text-2xl font-bold (adjust size as needed)
Body text should use: text-gray-600 dark:text-gray-300
Links should use: text-blue-600 hover:text-blue-800 dark:text-blue-400
Use flex and grid utilities for layouts instead of custom CSS
Use consistent spacing with gap-{size} and space-{x|y}-{size} utilities
Ensure responsive design using Tailwind responsive prefixes (sm:, md:, lg:, xl:)
Always include dark mode alternatives using the dark: prefix where appropriate
Use dark:bg-gray-800 for card backgrounds in dark mode
Use dark:text-gray-300 for text in dark mode
Maintain proper heading hierarchy (h1 > h2 > h3...)
Include appropriate ARIA labels and roles where needed
Ensure sufficient color contrast between text and background
Add visible hover and focus states for all interactive elements

Files:

  • web/templates/videos/list.html
web/templates/**/*.html

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

web/templates/**/*.html: Format HTML/Django templates with djlint using 120-character line length
Use Tailwind CSS classes only in templates (no custom CSS or inline styles)
Include dark mode variants using the "dark:" prefix in templates
Ensure proper HTML structure and accessibility in templates
Follow project color scheme in templates (teal-300, gray-600, green-600, yellow-600, red-600)

Files:

  • web/templates/videos/list.html
🔇 Additional comments (1)
web/templates/videos/list.html (1)

427-448: Good semantic HTML5 structure; dialog element and accessibility attributes are well-implemented.

The dialog element (line 428) includes:

  • Proper role="dialog" and aria-modal="true" attributes
  • aria-labelledby pointing to the title
  • Semantic <header>, <section> elements
  • Descriptive iframe title attribute
  • Screen-reader-only text for icon-only buttons

This is a solid foundation for accessible modal implementation.

Comment thread web/templates/videos/list.html
Comment thread web/templates/videos/list.html Outdated
Comment thread web/templates/videos/list.html Outdated
Comment thread web/templates/videos/list.html
- Remove redundant {% elif video.thumbnail_url %} block
- Fix copy-paste error that created identical conditional blocks
- Clean up template structure for better maintainability

The second condition was unreachable since the first already handled
the video.thumbnail_url case.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (4)
web/templates/videos/list.html (4)

219-221: Replace href="javascript:void(0)" with semantic href and event prevention.

Using javascript:void(0) is outdated and breaks progressive enhancement. If JavaScript fails to load, users cannot access the video. Use the actual video URL as href and prevent default navigation in the click handler.

Apply this diff to both video link locations:

- <a href="javascript:void(0)"
+ <a href="{{ video.video_url }}"
-    onclick="openVideoModal('{{ video.video_url }}', '{{ video.title|escapejs }}')"
+    onclick="event.preventDefault(); openVideoModal('{{ video.video_url }}', '{{ video.title|escapejs }}')"

Also applies to: 234-236


481-504: Avoid direct inline style manipulation; use class-based CSS transitions instead.

Lines 481–489 and 503–504 directly manipulate style.display, style.opacity, and style.transform. This:

  • Duplicates styling logic already in CSS (.show class, .video-modal-content transitions)
  • Makes animations brittle—CSS changes won't sync with JavaScript timeouts
  • Conflicts with Tailwind's responsive/dark-mode utilities
  • Violates separation of concerns

Refactored approach: control visibility and animation via class toggling only. Update CSS to handle all visual state transitions:

.video-modal {
  @apply hidden fixed inset-0 z-50 bg-black/90 backdrop-blur-sm opacity-0 transition-all duration-300 ease-out;
}

.video-modal.show {
  @apply flex items-center justify-center opacity-100;
}

.video-modal-content {
  @apply relative rounded-3xl overflow-hidden max-w-[95vw] w-full mx-2 shadow-2xl border border-white/20 scale-95 transition-all duration-300 ease-out;
}

.video-modal.show .video-modal-content {
  @apply scale-100;
}

Then simplify the JavaScript:

function openVideoModal(videoUrl, videoTitle) {
  // ... extract IDs, build embedUrl ...
  
  videoModalTitle.textContent = videoTitle;
  videoModalIframe.title = `Video player for ${videoTitle}`;
  videoModalIframe.src = embedUrl;
  
  // Show modal via class only (CSS handles display, opacity, transform)
  videoModal.style.display = 'flex';
  requestAnimationFrame(() => {
    videoModal.classList.add('show');
    document.body.style.overflow = 'hidden';
    videoModalClose.focus();
  });
}

function closeVideoModal() {
  videoModal.classList.remove('show');
  
  // Use transitionend or setTimeout to wait for CSS animation
  setTimeout(() => {
    videoModal.style.display = 'none';
    videoModalIframe.src = '';
    document.body.style.overflow = '';
  }, 300); // Match .video-modal transition-duration-300
}

513-517: Store and restore the triggering element for reliable focus management.

Line 514 checks document.activeElement.classList.contains('video-link'), but this is unreliable:

  • Focus moves to the close button immediately after the modal opens (line 497: videoModalClose.focus())
  • When the modal closes, document.activeElement is likely the close button or somewhere else, not the link that opened the modal
  • Screen readers may not announce the focus change correctly

Store a reference to the triggering element and restore it explicitly:

let triggeringElement = null;

function openVideoModal(videoUrl, videoTitle) {
  triggeringElement = document.activeElement; // Capture the element that triggered the modal
  // ... rest of modal opening logic ...
  videoModalClose.focus();
}

function closeVideoModal() {
  videoModal.classList.remove('show');
  
  setTimeout(() => {
    videoModal.style.display = 'none';
    videoModalIframe.src = '';
    document.body.style.overflow = '';
    
    // Restore focus to the element that opened the modal
    if (triggeringElement && document.contains(triggeringElement)) {
      triggeringElement.focus();
    }
  }, 300);
}

538-554: Improve tab-trapping selector to exclude disabled elements and handle edge cases.

Line 539 selects focusable elements but does not exclude disabled form controls or account for elements with tabindex:

const focusableElements = videoModal.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');

This can trap focus on disabled buttons or miss valid focusable elements. Additionally, if no focusable elements exist, firstElement and lastElement will be undefined, causing runtime errors.

Refactored selector:

if (e.key === 'Tab') {
  const focusableElements = videoModal.querySelectorAll(
    'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
  );
  
  if (focusableElements.length === 0) {
    // No focusable elements; prevent Tab from escaping
    e.preventDefault();
    return;
  }
  
  const firstElement = focusableElements[0];
  const lastElement = focusableElements[focusableElements.length - 1];
  const activeEl = document.activeElement;

  if (e.shiftKey) {
    if (activeEl === firstElement) {
      lastElement.focus();
      e.preventDefault();
    }
  } else {
    if (activeEl === lastElement) {
      firstElement.focus();
      e.preventDefault();
    }
  }
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 415b543 and ff36eaa.

📒 Files selected for processing (1)
  • web/templates/videos/list.html (4 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.html

📄 CodeRabbit inference engine (.cursorrules)

**/*.html: Always use Tailwind CSS utility classes for styling HTML elements
Never use inline styles on elements (no style="...")
Use the project color scheme via Tailwind classes: teal-300 (primary), gray-600 (secondary), green-600 (success), yellow-600 (warning), red-600 (danger), with dark: variants
Containers should use: container mx-auto px-4
Cards should use: bg-white dark:bg-gray-800 rounded-lg shadow-lg
Form inputs should use: w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500
Form labels should use: block text-sm font-medium text-gray-700 dark:text-gray-300
Primary buttons should use: bg-teal-300 hover:bg-teal-400 text-white px-6 py-2 rounded-lg transition duration-200
Secondary buttons should use: bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300
Headings use Tailwind typography: text-2xl font-bold (adjust size as needed)
Body text should use: text-gray-600 dark:text-gray-300
Links should use: text-blue-600 hover:text-blue-800 dark:text-blue-400
Use flex and grid utilities for layouts instead of custom CSS
Use consistent spacing with gap-{size} and space-{x|y}-{size} utilities
Ensure responsive design using Tailwind responsive prefixes (sm:, md:, lg:, xl:)
Always include dark mode alternatives using the dark: prefix where appropriate
Use dark:bg-gray-800 for card backgrounds in dark mode
Use dark:text-gray-300 for text in dark mode
Maintain proper heading hierarchy (h1 > h2 > h3...)
Include appropriate ARIA labels and roles where needed
Ensure sufficient color contrast between text and background
Add visible hover and focus states for all interactive elements

Files:

  • web/templates/videos/list.html
web/templates/**/*.html

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

web/templates/**/*.html: Format HTML/Django templates with djlint using 120-character line length
Use Tailwind CSS classes only in templates (no custom CSS or inline styles)
Include dark mode variants using the "dark:" prefix in templates
Ensure proper HTML structure and accessibility in templates
Follow project color scheme in templates (teal-300, gray-600, green-600, yellow-600, red-600)

Files:

  • web/templates/videos/list.html

Comment thread web/templates/videos/list.html
- Store reference to triggering element for reliable focus restoration
- Pass event object to openVideoModal function to capture caller
- Replace unreliable focus detection with explicit element reference
- Add proper cleanup of triggering element reference
- Ensure focus returns to the exact element that opened the modal
- Improve screen reader experience and keyboard navigation

Fixes focus restoration issues where focus could shift during modal
interaction, making the close button the active element instead of
the original triggering link.
- Enhance tab trapping logic to handle edge cases properly
- Add disabled element filtering to focusable elements selector
- Handle case where no focusable elements exist (prevent tab escape)
- Improve focus order by focusing title first for screen readers
- Add tabindex to modal title for better keyboard accessibility
- Ensure robust tab cycling between first and last elements
- Fix potential issues with iframe focusability

The improved implementation now properly handles:
- Disabled form elements (excluded from tab order)
- Empty focusable elements list (prevents tab escape)
- Better focus order (title -> close button)
- More robust element selection criteria
@A1L13N A1L13N removed the f1 label Dec 14, 2025
Copilot AI review requested due to automatic review settings December 15, 2025 00:29
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@github-actions github-actions Bot added the files-changed: 1 PR changes 1 file label Dec 15, 2025

// Function to extract Vimeo video ID
function getVimeoVideoId(url) {
const regExp = /^.*(vimeo\.com\/)((channels\/[A-z]+\/)|(groups\/[A-z]+\/videos\/)|(videos\/)|)([0-9]+)/;

Check warning

Code scanning / CodeQL

Overly permissive regular expression range Medium

Suspicious character range that is equivalent to [A-Z[]^_`a-z].

// Function to extract Vimeo video ID
function getVimeoVideoId(url) {
const regExp = /^.*(vimeo\.com\/)((channels\/[A-z]+\/)|(groups\/[A-z]+\/videos\/)|(videos\/)|)([0-9]+)/;

Check warning

Code scanning / CodeQL

Overly permissive regular expression range Medium

Suspicious character range that is equivalent to [A-Z[]^_`a-z].
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (1)
web/templates/videos/list.html (1)

26-143: Remove non-Tailwind CSS properties and refactor to pure Tailwind utilities.

The CSS block uses non-Tailwind properties (box-shadow, text-shadow, linear-gradient, clip) mixed with @apply directives, which violates the guideline requiring "Tailwind CSS classes only in templates (no custom CSS or inline styles)." While @apply is a Tailwind feature, adding custom CSS properties alongside it defeats the purpose of the utility-first approach.

Refactor by either:

  • Replacing box-shadow and text-shadow values with Tailwind shadow utilities (shadow-*, drop-shadow-*)
  • Converting linear-gradient overrides to Tailwind gradient utilities (bg-gradient-to-*)
  • Replacing clip: rect() with Tailwind's built-in sr-only utility

This aligns the code with the project's stated guidelines and improves maintainability.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ff36eaa and 6238960.

📒 Files selected for processing (1)
  • web/templates/videos/list.html (4 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
web/templates/**/*.html

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

web/templates/**/*.html: Format HTML/Django templates with djlint using 120-character line length
Use Tailwind CSS classes only in templates (no custom CSS or inline styles)
Include dark mode variants using the "dark:" prefix in templates
Ensure proper HTML structure and accessibility in templates
Follow project color scheme in templates (teal-300, gray-600, green-600, yellow-600, red-600)

Files:

  • web/templates/videos/list.html
**/*.{html,htm}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{html,htm}: Always use Tailwind CSS classes for styling HTML elements
Never use inline styles
Follow the project's color scheme using Tailwind's color classes (Primary: teal-300, Secondary: gray-600, Success: green-600, Warning: yellow-600, Danger: red-600, Dark mode variants using dark: prefix)
Use Tailwind container classes: container mx-auto px-4 for containers
Use Tailwind card classes: bg-white dark:bg-gray-800 rounded-lg shadow-lg for cards
Use Tailwind form input classes: w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 for form inputs
Use Tailwind form label classes: block text-sm font-medium text-gray-700 dark:text-gray-300 for form labels
Use Tailwind primary button classes: bg-teal-300 hover:bg-teal-400 text-white px-6 py-2 rounded-lg transition duration-200
Use Tailwind secondary button classes: bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300
Use Tailwind heading classes: text-2xl font-bold (adjust size as needed) for headings
Use Tailwind body text classes: text-gray-600 dark:text-gray-300 for body text
Use Tailwind link classes: text-blue-600 hover:text-blue-800 dark:text-blue-400 for links
Use flex and grid for layouts
Use consistent spacing with gap-{size} and space-{y|x}-{size} Tailwind classes
Use responsive design with Tailwind prefixes: sm:, md:, lg:, xl:
Always include dark mode alternatives using dark: prefix for elements
Use dark:bg-gray-800 for dark mode card backgrounds
Use dark:text-gray-300 for dark mode text
Use proper heading hierarchy for accessibility
Include proper ARIA labels where needed for accessibility
Ensure sufficient color contrast for accessibility
Add hover/focus states for interactive elements

Files:

  • web/templates/videos/list.html
**/*.{html,htm,css}

📄 CodeRabbit inference engine (AGENTS.md)

Never use custom CSS classes

Files:

  • web/templates/videos/list.html
🧠 Learnings (9)
📓 Common learnings
Learnt from: Abhishek-Punhani
Repo: alphaonelabs/alphaonelabs-education-website PR: 337
File: web/templates/courses/detail.html:1506-1560
Timestamp: 2025-04-07T11:33:41.277Z
Learning: For the alphaonelabs-education-website project, the team prefers to keep PRs focused and manageable in size. Additional enhancements like accessibility improvements should be addressed in separate follow-up issues rather than expanding the scope of an existing PR.
📚 Learning: 2025-04-05T16:36:48.539Z
Learnt from: Legacywave1
Repo: alphaonelabs/alphaonelabs-education-website PR: 457
File: web/templates/base.html:149-357
Timestamp: 2025-04-05T16:36:48.539Z
Learning: In the alphaonelabs-education-website project, avoid using nested CSS syntax like `.dark & {}` which is only valid in preprocessors like SCSS. Instead, use standard CSS selectors like `.dark .element-class {}` since the website appears to use standard CSS.

Applied to files:

  • web/templates/videos/list.html
📚 Learning: 2025-12-15T00:22:20.785Z
Learnt from: CR
Repo: alphaonelabs/alphaonelabs-education-website PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-15T00:22:20.785Z
Learning: Applies to **/*.{html,htm} : Use Tailwind primary button classes: bg-teal-300 hover:bg-teal-400 text-white px-6 py-2 rounded-lg transition duration-200

Applied to files:

  • web/templates/videos/list.html
📚 Learning: 2025-12-15T00:22:20.785Z
Learnt from: CR
Repo: alphaonelabs/alphaonelabs-education-website PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-15T00:22:20.785Z
Learning: Applies to **/*.{html,htm} : Use Tailwind secondary button classes: bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300

Applied to files:

  • web/templates/videos/list.html
📚 Learning: 2025-10-05T06:35:47.159Z
Learnt from: CR
Repo: alphaonelabs/alphaonelabs-education-website PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-05T06:35:47.159Z
Learning: Applies to web/templates/**/*.html : Use Tailwind CSS classes only in templates (no custom CSS or inline styles)

Applied to files:

  • web/templates/videos/list.html
📚 Learning: 2025-12-15T00:22:20.785Z
Learnt from: CR
Repo: alphaonelabs/alphaonelabs-education-website PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-15T00:22:20.785Z
Learning: Applies to **/*.{html,htm} : Follow the project's color scheme using Tailwind's color classes (Primary: teal-300, Secondary: gray-600, Success: green-600, Warning: yellow-600, Danger: red-600, Dark mode variants using dark: prefix)

Applied to files:

  • web/templates/videos/list.html
📚 Learning: 2025-12-15T00:22:20.785Z
Learnt from: CR
Repo: alphaonelabs/alphaonelabs-education-website PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-15T00:22:20.785Z
Learning: Applies to **/*.{html,htm} : Use Tailwind card classes: bg-white dark:bg-gray-800 rounded-lg shadow-lg for cards

Applied to files:

  • web/templates/videos/list.html
📚 Learning: 2025-12-15T00:22:20.785Z
Learnt from: CR
Repo: alphaonelabs/alphaonelabs-education-website PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-15T00:22:20.785Z
Learning: Applies to **/*.{html,htm} : Use Tailwind link classes: text-blue-600 hover:text-blue-800 dark:text-blue-400 for links

Applied to files:

  • web/templates/videos/list.html
📚 Learning: 2025-12-15T00:22:20.785Z
Learnt from: CR
Repo: alphaonelabs/alphaonelabs-education-website PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-15T00:22:20.785Z
Learning: Applies to **/*.{html,htm} : Use Tailwind form input classes: w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 for form inputs

Applied to files:

  • web/templates/videos/list.html
🪛 GitHub Check: CodeQL
web/templates/videos/list.html

[warning] 458-458: Overly permissive regular expression range
Suspicious character range that is equivalent to [A-Z\[\\]^_`a-z].


[warning] 458-458: Overly permissive regular expression range
Suspicious character range that is equivalent to [A-Z\[\\]^_`a-z].

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run Tests
🔇 Additional comments (2)
web/templates/videos/list.html (2)

416-437: Semantic HTML5 modal structure with strong accessibility foundation.

The modal markup uses proper semantic HTML (<dialog> element, <header>, <section>) with comprehensive ARIA attributes (role="dialog", aria-modal="true", aria-labelledby). The close button includes proper aria-label and sr-only text. The iframe includes a title attribute and appropriate allow permissions.

This foundation is solid and aligns with accessibility best practices.


548-574: Tab trapping implementation is complete and handles edge cases.

The keyboard navigation logic properly:

  • Selects focusable elements with appropriate exclusions (disabled, tabindex="-1")
  • Handles empty focusable list by preventing Tab escape
  • Cycles focus between first and last elements for both forward and backward Tab
  • Uses document.activeElement to track current focus context

This implementation aligns with WCAG focus management best practices.

Comment on lines +219 to +220
<a href="javascript:void(0)"
onclick="openVideoModal('{{ video.video_url }}', '{{ video.title|escapejs }}', event)"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Use meaningful href with preventDefault instead of javascript:void(0).

Lines 219 and 234 use the outdated href="javascript:void(0)" pattern. This breaks progressive enhancement and accessibility:

  • Users cannot right-click → open in new tab
  • JavaScript failures leave non-functional links
  • Screen readers may not recognize them as proper links

Apply this diff to both links:

-                      <a href="javascript:void(0)"
+                      <a href="{{ video.video_url }}"
-                         onclick="openVideoModal('{{ video.video_url }}', '{{ video.title|escapejs }}')"
+                         onclick="event.preventDefault(); openVideoModal('{{ video.video_url }}', '{{ video.title|escapejs }}', event)"
                         class="video-link absolute inset-0 flex items-center justify-center bg-black bg-opacity-25 hover:bg-opacity-0 transition-opacity">

And similarly for the title link:

-                    <a href="javascript:void(0)"
+                    <a href="{{ video.video_url }}"
-                       onclick="openVideoModal('{{ video.video_url }}', '{{ video.title|escapejs }}')"
+                       onclick="event.preventDefault(); openVideoModal('{{ video.video_url }}', '{{ video.title|escapejs }}', event)"
                        class="video-link hover:text-orange-500 transition-colors cursor-pointer">{{ video.title }}</a>

Also applies to: 234-235

🤖 Prompt for AI Agents
In web/templates/videos/list.html around lines 219-220 and 234-235, replace the
href="javascript:void(0)" pattern with a meaningful href that points to the
video URL (e.g. href="{{ video.video_url }}") and keep the onclick that opens
the modal, but update the onclick handler to accept the click event and call
event.preventDefault() inside the handler (openVideoModal(..., event)) so the
link remains a usable URL for right-click/open-in-new-tab and degrades
gracefully if JS is disabled; apply the same change to the title link and ensure
title is escaped as before.

Comment on lines +457 to +458
function getVimeoVideoId(url) {
const regExp = /^.*(vimeo\.com\/)((channels\/[A-z]+\/)|(groups\/[A-z]+\/videos\/)|(videos\/)|)([0-9]+)/;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Read the specific lines mentioned in the review
sed -n '450,460p' web/templates/videos/list.html

Repository: alphaonelabs/alphaonelabs-education-website

Length of output: 587


🏁 Script executed:

# Check if there are any CodeQL warnings or similar validation rules in the repo
fd -e json -e yml -e yaml | grep -i "codeql\|lint\|eslint" | head -20

Repository: alphaonelabs/alphaonelabs-education-website

Length of output: 69


🏁 Script executed:

# Check the git history for when this code was added and if there are any related issues
git log --oneline -n 20 -- web/templates/videos/list.html

Repository: alphaonelabs/alphaonelabs-education-website

Length of output: 159


🏁 Script executed:

# Check if there are other video ID validation functions or tests
rg "getVimeoVideoId|getYouTubeVideoId" -A 5 -B 2

Repository: alphaonelabs/alphaonelabs-education-website

Length of output: 1788


🏁 Script executed:

# Check if there are any other regex patterns that might have similar issues
rg "\[A-z\]" web/

Repository: alphaonelabs/alphaonelabs-education-website

Length of output: 228


Fix overly permissive regex character range [A-z] and enforce Vimeo ID validation.

The Vimeo URL regex at line 457–458 uses [A-z], which is overly permissive and includes unintended characters like [, \, ^, ` that fall between ASCII 'Z' (90) and 'a' (97).

Additionally, the regex should enforce a minimum of 8 digits for Vimeo video IDs to match the validation requirements:

    function getVimeoVideoId(url) {
-     const regExp = /^.*(vimeo\.com\/)((channels\/[A-z]+\/)|(groups\/[A-z]+\/videos\/)|(videos\/)|)([0-9]+)/;
+     const regExp = /^.*(vimeo\.com\/)((channels\/[A-Za-z]+\/)|(groups\/[A-Za-z]+\/videos\/)|(videos\/)|)([0-9]{8,})/;
      const match = url.match(regExp);
      return match ? match[6] : null;
    }
🧰 Tools
🪛 GitHub Check: CodeQL

[warning] 458-458: Overly permissive regular expression range
Suspicious character range that is equivalent to [A-Z\[\\]^_`a-z].


[warning] 458-458: Overly permissive regular expression range
Suspicious character range that is equivalent to [A-Z\[\\]^_`a-z].

🤖 Prompt for AI Agents
In web/templates/videos/list.html around lines 457–458, the Vimeo URL regex is
overly permissive because it uses [A-z] and it doesn't enforce Vimeo ID length;
replace the character class with a proper alphabetic class (e.g. [A-Za-z]) for
channel/group names and change the video ID capture to require at least 8 digits
(e.g. use \d{8,}); keep the overall structure of the alternations but update the
pattern so it only matches valid Vimeo segments and returns the numeric ID when
present.

Comment on lines +487 to +495
videoModal.style.display = 'flex';
videoModal.style.opacity = '0';
videoModal.querySelector('.video-modal-content').style.transform = 'scale(0.9) translateY(20px)';

// Trigger reflow and animate in
requestAnimationFrame(() => {
videoModal.classList.add('show');
videoModal.style.opacity = '1';
videoModal.querySelector('.video-modal-content').style.transform = 'scale(1) translateY(0)';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, check if the file exists and get its line count
wc -l web/templates/videos/list.html

Repository: alphaonelabs/alphaonelabs-education-website

Length of output: 121


🏁 Script executed:

# Read the specific section (487-510) mentioned in the review
sed -n '480,520p' web/templates/videos/list.html

Repository: alphaonelabs/alphaonelabs-education-website

Length of output: 1660


🏁 Script executed:

# Search for CSS class definitions related to video-modal
rg -A 5 "\.video-modal|\.show" web/templates/videos/list.html | head -100

Repository: alphaonelabs/alphaonelabs-education-website

Length of output: 3555


🏁 Script executed:

# Check for any <style> tags in the HTML file
grep -n "<style" web/templates/videos/list.html

Repository: alphaonelabs/alphaonelabs-education-website

Length of output: 98


🏁 Script executed:

# Get lines around 512 to see the setTimeout
sed -n '500,520p' web/templates/videos/list.html

Repository: alphaonelabs/alphaonelabs-education-website

Length of output: 900


🏁 Script executed:

# Also verify the exact structure of the openVideoModal function to understand the full context
sed -n '460,515p' web/templates/videos/list.html

Repository: alphaonelabs/alphaonelabs-education-website

Length of output: 2264


Remove inline style manipulation; use CSS class toggling for modal animations.

Lines 487–495 and 509–512 directly manipulate style.opacity and style.transform instead of relying on CSS transitions. This violates the coding guidelines ("Never use inline styles") and creates redundancy with CSS classes already defined for .video-modal.show and .video-modal-content.

Issues:

  • CSS defines transitions (duration-300, opacity-100, scale-100), but JavaScript overrides them with direct style assignments
  • Hardcoded setTimeout(..., 300) at line 512 depends on CSS duration; changing one breaks the other
  • Mixed concerns split styling between CSS and JavaScript

Fix: Use only CSS classes for animations. Remove inline style.opacity and style.transform manipulation:

function openVideoModal(videoUrl, videoTitle, event) {
  // ... existing code ...
  
  videoModal.style.display = 'flex';
  requestAnimationFrame(() => {
    videoModal.classList.add('show');
    document.body.style.overflow = 'hidden';
  });
  
  // ... rest of function ...
}

function closeVideoModal() {
  videoModal.classList.remove('show');
  
  setTimeout(() => {
    videoModal.style.display = 'none';
    videoModalIframe.src = '';
    document.body.style.overflow = '';
    if (triggeringElement && document.contains(triggeringElement)) {
      triggeringElement.focus();
    }
    triggeringElement = null;
  }, 300);
}

CSS already handles the rest via .video-modal.show and .video-modal.show .video-modal-content classes with proper transitions.

Also applies to: 509–512

🤖 Prompt for AI Agents
web/templates/videos/list.html lines 487-495 and 509-512: the JS currently sets
inline style.opacity and style.transform and uses a hardcoded timeout tied to
CSS durations; remove all inline style manipulation and rely on CSS class
toggling for show/hide animations instead. Change openVideoModal to set display
(if needed) then requestAnimationFrame to add the "show" class and set
document.body overflow, and change closeVideoModal to remove the "show" class
and use the existing CSS transition duration to hide after the same timeout (or
better, use transitionend event) before clearing the iframe, resetting display,
restoring body overflow and returning focus to the triggering element. Ensure no
direct style.opacity/transform assignments remain and keep the timeout value in
sync with CSS or replace it with transitionend.

videoModalIframe.src = embedUrl;

// Focus management for accessibility - focus title first for screen readers
videoModalTitle.focus();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider focus order in modal opening sequence.

Line 503 focuses on videoModalTitle when the modal opens. While this provides screen reader context, consider whether the close button (videoModalClose) might be a more intuitive first focus point for keyboard users.

Current approach (focus title first):

  • ✅ Screen readers announce title immediately
  • ⚠️ Long titles can be verbose

Alternative (focus close button):

  • ✅ Keyboard users can dismiss immediately
  • ✅ More standard modal pattern (close button as primary action)

This is not a blocker, but document the UX rationale if the current behavior is intentional.

🤖 Prompt for AI Agents
In web/templates/videos/list.html around line 503, the modal currently calls
videoModalTitle.focus(); update the modal-opening logic to focus videoModalClose
instead (so keyboard users land on the close control first) and ensure the modal
still exposes its title to assistive tech (e.g., via aria-labelledby or
role="dialog"); alternatively, if you intend to keep focusing the title, add an
inline comment above line 503 documenting the UX rationale (screen reader
priority vs keyboard dismissal) so the choice is explicit for future reviewers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

files-changed: 1 PR changes 1 file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: Open Educational Videos Directly on the Platform (Instead of Redirecting to YouTube)

4 participants