]> _ Git - odl.git/commitdiff
wait #5171 @3
authorVincent Vanwaelscappel <vincent@cubedesigners.com>
Wed, 23 Mar 2022 13:16:28 +0000 (14:16 +0100)
committerVincent Vanwaelscappel <vincent@cubedesigners.com>
Wed, 23 Mar 2022 13:16:28 +0000 (14:16 +0100)
app/Http/Controllers/FrontController.php
app/Jobs/ProcessFluidbook.php
app/Models/Publication.php
resources/pdfjs/build/pdf.js
resources/pdfjs/build/pdf.worker.js
resources/pdfjs/web/viewer.html
resources/precompiled.zip [new file with mode: 0644]
resources/views/components/media-library/index.blade.php
resources/views/front/media-library.blade.php
resources/views/front/media.blade.php [new file with mode: 0644]
resources/views/layouts/app.blade.php

index 14c9ca5357a8229942b8fe28aac37d4a9f10d6fd..1636c680bbc1338816cfedb13e5fe2657c445be7 100644 (file)
@@ -10,6 +10,8 @@ use App\Models\Resource;
 use App\Models\Settings;
 use Cubist\Backpack\Magic\PageData;
 use Cubist\Util\Files\Files;
+use FFMpeg\FFProbe;
+use SplFileInfo;
 
 class FrontController extends Controller
 {
@@ -65,12 +67,59 @@ class FrontController extends Controller
 
     protected function _getDataFromCMS()
     {
-        return ['menu' => $this->_getMenuFromCMS(),
+        $res = ['menu' => $this->_getMenuFromCMS(),
             'resources' => $this->_getResourcesFromCMS(),
             'home' => $this->_getHomeFromCMS(),
             'medialibrary' => $this->_getMediaLibraryFromCMS(),
             'resources_pdf' => $this->_getResourcesPDFFromCMS(),
             'settings' => $this->_getSettingsFromCMS()];
+
+        $theme_order = [1, 5, 2, 6, 3, 7, 4, 8, 9, 10, 11, 12, 13, 14, 15, 16];
+
+        $themes = [];
+        foreach ($theme_order as $id) {
+            if (isset($res['medialibrary']['themes'][$id])) {
+                $themes[$id] = $res['medialibrary']['themes'][$id];
+            }
+        }
+        $res['theme_order'] = $theme_order;
+        $res['themes'] = $themes;
+        $res['media_types'] = [
+            'video' => 'Vidéos',
+            'audio' => 'Podcast',
+        ];
+
+        $media=[];
+        $ffprobe = FFProbe::create();
+        foreach ($res['medialibrary']['assets'] as $a) {
+            /** @var $a \Cubist\Backpack\Magic\PageData */
+            $file=$a->getMediaPathsByCollection($a->get('file_upload'))[0]??null;
+            if(null===$file){
+                continue;
+            }
+
+            $spf=new SplFileInfo($file);
+            $ext=mb_strtolower($spf->getExtension());
+            if(in_array($ext,['pdf','jpg','jpeg','gif','png'])){
+                continue;
+            }
+            $map=['mp3'=>'audio','mp4'=>'video'];
+            $type=$map[$ext]??$a->get('type');
+
+            $media[$a['id']]=[
+                'id'=>$a->get('id'),
+                'type'=>$type,
+                'theme'=>$a->get('theme'),
+                'code'=>$a->get('code'),
+                'title'=>$a->get('title'),
+                'file'=>$a->getImageURLbyCollection($a->get('file_upload')),
+                'image'=>$a->getImageURLbyCollection($a->get('file_thumb'))??$a->getImageURLbyCollection($a->get('file_upload'),'poster'),
+                'duration'=>$ffprobe->format($file)->get('duration')
+            ];
+        }
+        $res['media']=$media;
+
+        return $res;
     }
 
     protected function _getMenuFromCMS()
index d4b0bf159d96f7084319d6e3547af3c2ff5e3fb2..67e975631eb92d0e4f2f0152933567f5eefbe947 100644 (file)
@@ -4,7 +4,9 @@ namespace App\Jobs;
 
 use App\Models\Asset;
 use App\Models\Publication;
+use Cubist\Backpack\Magic\PageData;
 use Cubist\Util\Files\Files;
+use Cubist\Util\Text;
 use Cubist\Util\Zip;
 use Fluidbook\Tools\Compiler\Compiler;
 use Spatie\MediaLibrary\MediaCollections\Models\Media;
@@ -29,7 +31,7 @@ class ProcessFluidbook extends Compiler
         $this->addSource($media->getPath());
 
         /** @var Media $precompiled */
-        $precompiled = $this->pub->getMediaInField($this->pub->getAttributeValue('precompiled'))->first()->getPath();
+        $precompiled = resource_path('precompiled.zip');
         $stubuid = md5($precompiled . '//' . filemtime($precompiled) . '///' . filesize($precompiled));
         $this->stub = storage_path('fluidbook/stubs/' . $stubuid . '/');
         if (!file_exists($this->stub)) {
@@ -132,12 +134,18 @@ class ProcessFluidbook extends Compiler
         $this->config->mediaIDToFile = [];
         $res = [];
 
+        // Add media
+        $assets = PageData::fromEntities(Asset::getAssetsInMediaLibrary());
+        foreach ($assets as $asset) {
+            $links[] = ['page' => 'background', 'to' => 'video-popup:' . $asset->id, 'left' => -1, 'top' => -1, 'width' => 1, 'height' => 1, 'uid' => 'media_' . $asset->id];
+        }
+
         foreach ($links as $k => $link) {
             $e = explode(':', $link['to']);
-            $ee = explode('/', $e[0]);
+            $ee = Text::multiExplode('/-', $e[0]);
 
             if ($ee[0] == 'anim') {
-                $base = [['type'=>'','ease' => 'Power2.easeOut', 'duration' => 1.5, 'transformOrigin' => 'center', 'delay' => 1], []];
+                $base = [['type' => '', 'ease' => 'Power2.easeOut', 'duration' => 1.5, 'transformOrigin' => 'center', 'delay' => 1], []];
                 switch ($ee[1]) {
                     case 'fadein':
                         $link['type'] = 14;
@@ -211,7 +219,7 @@ class ProcessFluidbook extends Compiler
                     $link['loop'] = false;
                     $link['sound'] = true;
                 }
-            } else if (stripos($e[0], 'slideshow/') === 0) {
+            } else if ($ee[0] === 'slideshow') {
                 $link['inline'] = $ee[1] === 'inline';
                 $link['to'] = $this->_zipAssets($e[1]);
                 if ($link['inline']) {
index 578d858cc55f4a078ab222464118c4dfa417a16f..925338f782ca223a70774c3b7faa8cb66e8fcb4c 100644 (file)
@@ -24,8 +24,6 @@ class Publication extends CubistMagicAbstractModel
         parent::setFields();
 
         $this->addField('document', Files::class, 'Document', ['tab' => 'Publication interactive']);
-        $this->addField('precompiled', Files::class, 'Fluidbook Précompilé', ['can' => 'precompiled', 'tab' => 'Publication interactive']);
-        $this->addField('animdemo', Files::class, 'Fluidbook Démo', ['can' => 'precompiled', 'tab' => 'Publication interactive']);
         $this->addField('logo', Images::class, 'Logo', ['tab' => 'Contenus']);
         $this->addField('subtitle', Text::class, 'Sous-titre', ['tab' => 'Contenus']);
         $this->addField('illustration', Images::class, 'Image', ['tab' => 'Contenus']);
index e7a603123522d11cdc41ff85f775d0b51be51386..4c10cab665eaf159829eafcff7a0487f53ed9d21 100644 (file)
@@ -975,6 +975,10 @@ function _isValidProtocol(url) {
     case "tel:":
     case "pdf:":
     case "tooltip:":
+  case "video/popup:":
+  case "video/inline:":
+  case "video-popup:":
+  case "video-inline:":
       return true;
 
     default:
index e317b3d71f21a8f390de6127f72b68db9984b182..310c9f10b6fbbffab54e87b08c34180e875d953b 100644 (file)
                         case "tel:":
                         case "pdf:":
                         case "tooltip:":
+                        case "video/popup:":
+                        case "video/inline:":
+                        case "video-popup:":
+                        case "video-inline:":
                             return true;
 
                         default:
index 92bc0c62a45d22543a8ddb7ea068b3e793152169..1d7cd8995c17296b8239623d34ccacf9846e023a 100644 (file)
@@ -41,6 +41,18 @@ See https://github.com/adobe-type-tools/cmap-resources
                 return true;
             });
 
+            $(document).on('click', 'a[href^="video"]', function () {
+                var e = $(this).attr('href').split(':');
+                var videoid = e[1];
+
+                if (inFluidbook) {
+                    window.parent.fluidbook.links.triggerLinkById('media_' + videoid);
+                } else {
+                    window.parent.openVideo(videoid);
+                }
+                return false;
+            });
+
             $(document).on('click', 'a[href^="pdf:"]', function () {
                 var e = $(this).attr('href').split('#');
                 var file;
diff --git a/resources/precompiled.zip b/resources/precompiled.zip
new file mode 100644 (file)
index 0000000..8595cf6
Binary files /dev/null and b/resources/precompiled.zip differ
index 32d951d117dd137f5fcd80798e0063abccce346e..ce2a7510ac875676eb67e5565b707d3630163b92 100644 (file)
     {{-- MEDIA LIBRARY GRID --}}
     {{-- Negative margins applied here to offset margins used in Isotope grid --}}
     <div class="media-grid opacity-0 mt-10 -mb-16 -mx-2.5" x-init="initIsotope($el)">
-        @foreach ($media as $item)
-
-            @php
-                $filters = [];
-                if (!is_array($item['theme'])) {
-                    $item['theme'] = [$item['theme']];
-                }
-                foreach ($item['theme'] as $t) {
-                    $filters[] = 'theme-'.$t;
-                }
-                $theme = $themes[$item['theme'][0]];
-                $player_data = [
-                    'ID' => $item['id'],
-                    'player' => [
-                        'type' => $item['type'], // video / audio
-                        'sources' => [
-                            [
-                                'src' => $item['file'],
-                            ]
-                        ]
-                    ],
-                    'title' => $item['title'],
-                    'image' => $item['image'],
-                ];
-            @endphp
-
-            <div id="media_{{ $item['id'] }}"
-                 @click="$dispatch('player-open', { sourceData: JSON.parse($el.dataset.player) })"
-                 data-player='{{ json_encode($player_data) }}'
-                 class="media-item group cursor-pointer
-                        {{-- Width is 25% minus the gutters (2 * 0.625rem that comes from mx-2.5) --}}
-                     float-left w-[calc(100%/2-1.25rem)] md:w-[calc(100%/3-1.25rem)] lg:w-[calc(100%/4-1.25rem)] mx-2.5 mb-16
-                     {{ implode(' ',$filters) }}
-                     ">
-                <div class=" media-item-image relative bg-cover bg-no-repeat rounded-md pb-[56.25%]"
-                     style="background-color:#ddd;background-image:url({{ $item['image'] }})">
-
-                    {{-- Play Icon --}}
-                    <svg
-                        class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 transition group-hover:scale-110"
-                        xmlns="http://www.w3.org/2000/svg" width="22.47%" viewBox="0 0 59.999 59.999">
-                        {{-- Icon BG --}}
-                        <path d="M0 29.999a30 30 0 1 0 30-30 30 30 0 0 0-30 30Z" fill="#0725e2"/>
-
-                        <g fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
-                            @if ($item['type'] === 'audio')
-                                {{-- Audio Play Icon --}}
-                                <path d="M15.999 25.324h5.324v9.317h-5.324Z"/>
-                                <path d="M35.321 24.635a7.561 7.561 0 0 1 0 10.693 7.523 7.523 0 0 0 0-10.693Z"/>
-                                <path d="M39.644 21.627a11.815 11.815 0 0 1 0 16.709 11.756 11.756 0 0 0 0-16.709Z"/>
-                                <path d="M21.323 25.324 30.64 20v19.966l-9.317-5.325Z"/>
-                            @else
-                                {{-- Video Play Icon --}}
-                                <path d="m26.5 38.5 12-8-12-8Z"/>
-                            @endif
-                        </g>
-                    </svg>
-
-                    <x-duration class="absolute bottom-2 right-2 font-secondary text-white"
-                                style="text-shadow: rgb(0 0 0 / 50%) 0 0.1em 0.2em">
-                        {{ $item['duration'] }}
-                    </x-duration>
-                </div>
-                {{-- THEME LABEL --}}
-                <div class="mt-2.5 font-secondary font-medium text-xs leading-none" style="color:{{ $theme['color'] }}">
-                    {{ $theme['name'] }}
-                </div>
-                {{-- Set min height of 2 lines so intial float layout is less likely to have misplaced blocks --}}
-                <div class="mt-1.5 font-semibold leading-snug min-h-[2.75em]">
-                    ({{$item['code']}}) {{ $item['title'] }}
-                </div>
-            </div>
-        @endforeach
+        @include('front.media')
     </div>
 
     <x-media-library.filter-interface/>
index 2dca625d0ffc27aac9cf99943d36e8a5cf72b3da..fda07084f35b534e72ff6655e9d499a27ac6ddc1 100644 (file)
@@ -1,52 +1,6 @@
 @extends('layouts.app')
 
 @section('content')
-
-    @php
-        $media_types = [
-            'video' => 'Vidéos',
-            'audio' => 'Podcast',
-        ];
-
-        $theme_order=[1,5,2,6,3,7,4,8,9,10,11,12,13,14,15,16];
-
-        $themes=[];
-        foreach ($theme_order as $id){
-            if(isset($medialibrary['themes'][$id])){
-                $themes[$id]=$medialibrary['themes'][$id];
-            }
-        }
-
-        $media=[];
-        $ffprobe = FFMpeg\FFProbe::create();
-        foreach ($medialibrary['assets'] as $a) {
-            /** @var $a \Cubist\Backpack\Magic\PageData */
-            $file=$a->getMediaPathsByCollection($a->get('file_upload'))[0]??null;
-            if(null===$file){
-                continue;
-            }
-
-            $spf=new SplFileInfo($file);
-            $ext=mb_strtolower($spf->getExtension());
-            if(in_array($ext,['pdf','jpg','jpeg','gif','png'])){
-                continue;
-            }
-            $map=['mp3'=>'audio','mp4'=>'video'];
-            $type=$map[$ext]??$a->get('type');
-
-            $media[$a['id']]=[
-                'id'=>$a->get('id'),
-                'type'=>$type,
-                'theme'=>$a->get('theme'),
-                'code'=>$a->get('code'),
-                'title'=>$a->get('title'),
-                'file'=>$a->getImageURLbyCollection($a->get('file_upload')),
-                'image'=>$a->getImageURLbyCollection($a->get('file_thumb'))??$a->getImageURLbyCollection($a->get('file_upload'),'poster'),
-                'duration'=>$ffprobe->format($file)->get('duration')
-               ];
-        }
-    @endphp
-
     <div class="overflow-hidden py-2">{{-- vertical padding so accents don't get cropped --}}
         <h1 class="uppercase animate animate-in-up"
             style="--animation-duration: 0.7s;">
diff --git a/resources/views/front/media.blade.php b/resources/views/front/media.blade.php
new file mode 100644 (file)
index 0000000..a82291a
--- /dev/null
@@ -0,0 +1,73 @@
+@foreach ($media as $item)
+
+    @php
+        $filters = [];
+        if (!is_array($item['theme'])) {
+            $item['theme'] = [$item['theme']];
+        }
+        foreach ($item['theme'] as $t) {
+            $filters[] = 'theme-'.$t;
+        }
+        $theme = $themes[$item['theme'][0]];
+        $player_data = [
+            'ID' => $item['id'],
+            'player' => [
+                'type' => $item['type'], // video / audio
+                'sources' => [
+                    [
+                        'src' => $item['file'],
+                    ]
+                ]
+            ],
+            'title' => $item['title'],
+            'image' => $item['image'],
+        ];
+    @endphp
+
+    <div id="media_{{ $item['id'] }}"
+         @click="$dispatch('player-open', { sourceData: JSON.parse($el.dataset.player) })"
+         data-player='{{ json_encode($player_data) }}'
+         class="media-item group cursor-pointer
+                        {{-- Width is 25% minus the gutters (2 * 0.625rem that comes from mx-2.5) --}}
+             float-left w-[calc(100%/2-1.25rem)] md:w-[calc(100%/3-1.25rem)] lg:w-[calc(100%/4-1.25rem)] mx-2.5 mb-16
+{{ implode(' ',$filters) }}
+             ">
+        <div class=" media-item-image relative bg-cover bg-no-repeat rounded-md pb-[56.25%]"
+             style="background-color:#ddd;background-image:url({{ $item['image'] }})">
+
+            {{-- Play Icon --}}
+            <svg
+                class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 transition group-hover:scale-110"
+                xmlns="http://www.w3.org/2000/svg" width="22.47%" viewBox="0 0 59.999 59.999">
+                {{-- Icon BG --}}
+                <path d="M0 29.999a30 30 0 1 0 30-30 30 30 0 0 0-30 30Z" fill="#0725e2"/>
+
+                <g fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
+                    @if ($item['type'] === 'audio')
+                        {{-- Audio Play Icon --}}
+                        <path d="M15.999 25.324h5.324v9.317h-5.324Z"/>
+                        <path d="M35.321 24.635a7.561 7.561 0 0 1 0 10.693 7.523 7.523 0 0 0 0-10.693Z"/>
+                        <path d="M39.644 21.627a11.815 11.815 0 0 1 0 16.709 11.756 11.756 0 0 0 0-16.709Z"/>
+                        <path d="M21.323 25.324 30.64 20v19.966l-9.317-5.325Z"/>
+                    @else
+                        {{-- Video Play Icon --}}
+                        <path d="m26.5 38.5 12-8-12-8Z"/>
+                    @endif
+                </g>
+            </svg>
+
+            <x-duration class="absolute bottom-2 right-2 font-secondary text-white"
+                        style="text-shadow: rgb(0 0 0 / 50%) 0 0.1em 0.2em">
+                {{ $item['duration'] }}
+            </x-duration>
+        </div>
+        {{-- THEME LABEL --}}
+        <div class="mt-2.5 font-secondary font-medium text-xs leading-none" style="color:{{ $theme['color'] }}">
+            {{ $theme['name'] }}
+        </div>
+        {{-- Set min height of 2 lines so intial float layout is less likely to have misplaced blocks --}}
+        <div class="mt-1.5 font-semibold leading-snug min-h-[2.75em]">
+            ({{$item['code']}}) {{ $item['title'] }}
+        </div>
+    </div>
+@endforeach
index be46663d51149de541e22b5d4dd28b9bf4186a62..4e503d562068856783981434a71fc6b694963a7d 100644 (file)
                 },
             }
         }
+
+        function simulateClick(elem) {
+            // Create our event (with options)
+            var evt = new MouseEvent('click', {
+                bubbles: true,
+                cancelable: true,
+                view: window
+            });
+            // If cancelled, don't dispatch our event
+            var canceled = !elem.dispatchEvent(evt);
+        }
+
+        function openVideo(id) {
+            simulateClick(document.getElementById('media_' + id));
+        }
     </script>
 @endpush
 
     <body x-data="app()"
           @resize.window.debounce="measureWidth()"
           class="font-primary p-8 @stack('extra_body_classes')">
-@endsection
+    @endsection
 
-@section('main')
-    {{--
-        Content has constrained width so that nothing shifts when overlay is opened and scrolling is disabled.
-        The body width changes when scrollbars are removed, which causes alignment problems, especially with
-        fixed-position elements.
-    --}}
-    <div style="width: var(--content-width)">
-        <x-header main="true"/>
-
-        <div class="min-h-content flex flex-col mx-22 pt-22 opacity-0" :class="{ 'opacity-0': maskContents }">
-            @yield('content')
+    @section('main')
+        <div id="mediahidden" style="display: none">
+            @include('front.media')
+        </div>
+        {{--
+            Content has constrained width so that nothing shifts when overlay is opened and scrolling is disabled.
+            The body width changes when scrollbars are removed, which causes alignment problems, especially with
+            fixed-position elements.
+        --}}
+        <div style="width: var(--content-width)">
+            <x-header main="true"/>
+
+            <div class="min-h-content flex flex-col mx-22 pt-22 opacity-0" :class="{ 'opacity-0': maskContents }">
+                @yield('content')
+            </div>
         </div>
-    </div>
 
 
 
-    {{-- MENU OVERLAY --}}
-    <div class="overlay menu-overlay
+        {{-- MENU OVERLAY --}}
+        <div class="overlay menu-overlay
             bg-blue text-white
             z-30"
-         x-show="menuOpen"
-         x-transition.opacity.duration.500ms
-         x-transition:leave.opacity.duration.500ms.delay.500ms
-         x-cloak>
+             x-show="menuOpen"
+             x-transition.opacity.duration.500ms
+             x-transition:leave.opacity.duration.500ms.delay.500ms
+             x-cloak>
 
-        <x-header logo-class="text-white"/>
+            <x-header logo-class="text-white"/>
 
-        <ul class="w-full font-medium text-6xl">
-            @foreach ($menu as $link)
+            <ul class="w-full font-medium text-6xl">
+                @foreach ($menu as $link)
 
-                @php
-                    $prevent='menuOpen = false; setTimeout(() => window.location = $event.target.href, 200);';
-                    if($link['type']==='pdf'){
-                        $prevent='menuOpen = false;openPDF($el.attributes.href.value)';
-                    }
-                @endphp
-                {{-- Overflow is hidden for text entrance animation. --}}
-                {{-- Extra padding and negative margin added so hover scale effect isn't clipped --}}
-                <li class="overflow-hidden pl-4 -ml-4">
-                    <div x-show="menuOpen"
-                         class="transition transform ease-out-quint"
-                         style="transition-delay: {{ 250 + (50 * $loop->index) }}ms"
-                         x-transition:enter="duration-1000"
-                         x-transition:enter-start="translate-y-[100px]"
-                         x-transition:enter-end="translate-x-0"
-                         x-transition:leave="duration-500"
-                         x-transition:leave-start="translate-x-0"
-                         x-transition:leave-end="translate-y-[100px]">
-                        <x-link
-                            href="{{ $link['url'] }}"
-                            @click.prevent="{!! $prevent !!}"
-                            class="block py-8 text-current
+                    @php
+                        $prevent='menuOpen = false; setTimeout(() => window.location = $event.target.href, 200);';
+                        if($link['type']==='pdf'){
+                            $prevent='menuOpen = false;openPDF($el.attributes.href.value)';
+                        }
+                    @endphp
+                    {{-- Overflow is hidden for text entrance animation. --}}
+                    {{-- Extra padding and negative margin added so hover scale effect isn't clipped --}}
+                    <li class="overflow-hidden pl-4 -ml-4">
+                        <div x-show="menuOpen"
+                             class="transition transform ease-out-quint"
+                             style="transition-delay: {{ 250 + (50 * $loop->index) }}ms"
+                             x-transition:enter="duration-1000"
+                             x-transition:enter-start="translate-y-[100px]"
+                             x-transition:enter-end="translate-x-0"
+                             x-transition:leave="duration-500"
+                             x-transition:leave-start="translate-x-0"
+                             x-transition:leave-end="translate-y-[100px]">
+                            <x-link
+                                href="{{ $link['url'] }}"
+                                @click.prevent="{!! $prevent !!}"
+                                class="block py-8 text-current
                                transform origin-bottom-left
                                transition-transform duration-200
                                hover:scale-105">
-                            {{ $link['title'] }}
-                        </x-link>
-                    </div>
-                </li>
-                <li class="bg-blue-dark h-px
+                                {{ $link['title'] }}
+                            </x-link>
+                        </div>
+                    </li>
+                    <li class="bg-blue-dark h-px
                        transform origin-left
                        transition ease-out-quint"
-                    x-show="menuOpen"
-                    style="transition-delay: {{ 250 + (50 * $loop->index) }}ms"
-                    x-transition:enter="duration-2000"
-                    x-transition:enter-start="scale-x-0"
-                    x-transition:enter-end="scale-x-100"
-                    x-transition:leave="duration-500"
-                    x-transition:leave-start="scale-x-100"
-                    x-transition:leave-end="scale-x-0">
-                </li>
-            @endforeach
-        </ul>
-    </div>
-
-    {{-- PDF Viewer Overlay --}}
-    <div class="overlay pdf-overlay
+                        x-show="menuOpen"
+                        style="transition-delay: {{ 250 + (50 * $loop->index) }}ms"
+                        x-transition:enter="duration-2000"
+                        x-transition:enter-start="scale-x-0"
+                        x-transition:enter-end="scale-x-100"
+                        x-transition:leave="duration-500"
+                        x-transition:leave-start="scale-x-100"
+                        x-transition:leave-end="scale-x-0">
+                    </li>
+                @endforeach
+            </ul>
+        </div>
+
+        {{-- PDF Viewer Overlay --}}
+        <div class="overlay pdf-overlay
                 bg-white text-black
                 pt-20
                 z-20"
-         x-show="PDFOpen"
-         x-transition:enter.opacity.duration.500ms
-         x-transition:leave.opacity.duration.200ms
-         x-cloak>
-
-        {{-- PDF Viewer iframe --}}
-        <iframe x-ref="PDFViewer" src="" frameborder="0"
-                class="absolute left-0 w-full top-[90px] h-[calc(100vh-90px)]"></iframe>
-
-        <x-header>
-            <x-slot name="right">
-                <div x-show="PDFOpen && !menuOpen" x-transition:enter.opacity.duration.500ms.delay.300ms
-                     x-transition:leave.opacity.duration.0ms.delay.0ms>
-                    <x-close
-                        x-show="PDFOpen"
-                        @click.prevent="closePDF()"
-                        class="flex"
-                    />
-                </div>
-            </x-slot>
-        </x-header>
-    </div>
-
-    {{-- Media Player --}}
-    <x-media-library.player/>
-
-    {{-- Search Overlay (placed last so it sits above other modals, like the PDF viewer, with the same z-index) --}}
-    <x-search/>
+             x-show="PDFOpen"
+             x-transition:enter.opacity.duration.500ms
+             x-transition:leave.opacity.duration.200ms
+             x-cloak>
+
+            {{-- PDF Viewer iframe --}}
+            <iframe x-ref="PDFViewer" src="" frameborder="0"
+                    class="absolute left-0 w-full top-[90px] h-[calc(100vh-90px)]"></iframe>
+
+            <x-header>
+                <x-slot name="right">
+                    <div x-show="PDFOpen && !menuOpen" x-transition:enter.opacity.duration.500ms.delay.300ms
+                         x-transition:leave.opacity.duration.0ms.delay.0ms>
+                        <x-close
+                            x-show="PDFOpen"
+                            @click.prevent="closePDF()"
+                            class="flex"
+                        />
+                    </div>
+                </x-slot>
+            </x-header>
+        </div>
+
+        {{-- Media Player --}}
+        <x-media-library.player/>
+
+        {{-- Search Overlay (placed last so it sits above other modals, like the PDF viewer, with the same z-index) --}}
+        <x-search/>
 
 @endsection