1- // Modern Certificate Gallery with Tailwind CSS and shadcn/ui theming
21document . addEventListener ( "DOMContentLoaded" , function ( ) {
3- // Certificate data with more metadata for better display
4- const certificates = [
5- { path : 'data/UdacityCertificate.pdf' , title : 'Udacity Certificate' , category : 'education' } ,
6- { path : 'data/Coursera E4WZRUCIBAMB.pdf' , title : 'Coursera Certificate' , category : 'education' } ,
7- { path : 'data/CertificateOfCompletion_Master JavaScript.pdf' , title : 'Master JavaScript' , category : 'javascript' } ,
8- { path : 'data/CertificateOfCompletion_Career Essentials in Generative AI by Microsoft and LinkedIn.pdf' , title : 'Generative AI Essentials' , category : 'ai' } ,
9- { path : 'data/CertificateOfCompletion_Advanced Node.js.pdf' , title : 'Advanced Node.js' , category : 'node' } ,
10- { path : 'data/CertificateOfCompletion_CSS Essential Training.pdf' , title : 'CSS Essential Training' , category : 'css' } ,
11- { path : 'data/CertificateOfCompletion_EndtoEnd JavaScript Testing with Cypress.io.pdf' , title : 'Cypress.io Testing' , category : 'javascript' } ,
12- { path : 'data/CertificateOfCompletion_Ethical Hacking with JavaScript.pdf' , title : 'Ethical Hacking' , category : 'javascript' } ,
13- { path : 'data/CertificateOfCompletion_JavaScript Best Practices for Code Formatting.pdf' , title : 'JS Code Formatting' , category : 'javascript' } ,
14- { path : 'data/CertificateOfCompletion_JavaScript Best Practices for Data.pdf' , title : 'JS Data Practices' , category : 'javascript' } ,
15- { path : 'data/CertificateOfCompletion_JavaScript Security Essentials.pdf' , title : 'JS Security' , category : 'javascript' } ,
16- { path : 'data/CertificateOfCompletion_JavaScript TestDriven Development ES6.pdf' , title : 'Test-Driven Development' , category : 'javascript' } ,
17- { path : 'data/CertificateOfCompletion_Learning MongoDB.pdf' , title : 'MongoDB' , category : 'database' } ,
18- { path : 'data/CertificateOfCompletion_Node.js Design Patterns.pdf' , title : 'Node.js Design Patterns' , category : 'node' } ,
19- { path : 'data/CertificateOfCompletion_Responsive Layout.pdf' , title : 'Responsive Layout' , category : 'css' } ,
20- { path : 'data/Gabrielius Pocevicius_JavaScript.pdf' , title : 'JavaScript Certificate' , category : 'javascript' } ,
21- { path : 'data/Gabrielius Pocevicius_Python.pdf' , title : 'Python Certificate' , category : 'python' } ,
22- { path : 'data/CertificateOfCompletion_Search Techniques for Web Developers.pdf' , title : 'Search Techniques' , category : 'development' } ,
23- { path : 'data/CertificateOfCompletion_Essential New Skills in Software Engineering.pdf' , title : 'Software Engineering Skills' , category : 'development' } ,
24- ] ;
2+ // Check if we've already loaded certificates to prevent duplicates
3+ if ( window . certificatesLoaded ) return ;
4+ window . certificatesLoaded = true ;
255
26- let loadedCount = 0 ;
27- const totalCerts = certificates . length ;
28- const certContainer = document . getElementById ( 'cert-container' ) ;
29- const loadingIndicator = document . getElementById ( 'loading-indicator' ) ;
6+ // Certificate data with more metadata for better display
7+ const certificates = [
8+ { path : 'data/UdacityCertificate.pdf' , title : 'Udacity Certificate' , category : 'education' } ,
9+ { path : 'data/Coursera E4WZRUCIBAMB.pdf' , title : 'Coursera Certificate' , category : 'education' } ,
10+ { path : 'data/CertificateOfCompletion_Master JavaScript.pdf' , title : 'Master JavaScript' , category : 'javascript' } ,
11+ { path : 'data/CertificateOfCompletion_Career Essentials in Generative AI by Microsoft and LinkedIn.pdf' , title : 'Generative AI Essentials' , category : 'ai' } ,
12+ { path : 'data/CertificateOfCompletion_Advanced Node.js.pdf' , title : 'Advanced Node.js' , category : 'node' } ,
13+ { path : 'data/CertificateOfCompletion_CSS Essential Training.pdf' , title : 'CSS Essential Training' , category : 'css' } ,
14+ { path : 'data/CertificateOfCompletion_EndtoEnd JavaScript Testing with Cypress.io.pdf' , title : 'Cypress.io Testing' , category : 'javascript' } ,
15+ { path : 'data/CertificateOfCompletion_Ethical Hacking with JavaScript.pdf' , title : 'Ethical Hacking' , category : 'javascript' } ,
16+ { path : 'data/CertificateOfCompletion_JavaScript Best Practices for Code Formatting.pdf' , title : 'JS Code Formatting' , category : 'javascript' } ,
17+ { path : 'data/CertificateOfCompletion_JavaScript Best Practices for Data.pdf' , title : 'JS Data Practices' , category : 'javascript' } ,
18+ { path : 'data/CertificateOfCompletion_JavaScript Security Essentials.pdf' , title : 'JS Security' , category : 'javascript' } ,
19+ { path : 'data/CertificateOfCompletion_JavaScript TestDriven Development ES6.pdf' , title : 'Test-Driven Development' , category : 'javascript' } ,
20+ { path : 'data/CertificateOfCompletion_Learning MongoDB.pdf' , title : 'MongoDB' , category : 'database' } ,
21+ { path : 'data/CertificateOfCompletion_Node.js Design Patterns.pdf' , title : 'Node.js Design Patterns' , category : 'node' } ,
22+ { path : 'data/CertificateOfCompletion_Responsive Layout.pdf' , title : 'Responsive Layout' , category : 'css' } ,
23+ { path : 'data/Gabrielius Pocevicius_JavaScript.pdf' , title : 'JavaScript Certificate' , category : 'javascript' } ,
24+ { path : 'data/Gabrielius Pocevicius_Python.pdf' , title : 'Python Certificate' , category : 'python' } ,
25+ { path : 'data/CertificateOfCompletion_Search Techniques for Web Developers.pdf' , title : 'Search Techniques' , category : 'development' } ,
26+ { path : 'data/CertificateOfCompletion_Essential New Skills in Software Engineering.pdf' , title : 'Software Engineering Skills' , category : 'development' } ,
27+ ] ;
28+
29+ let loadedCount = 0 ;
30+ const totalCerts = certificates . length ;
31+ const certContainer = document . getElementById ( 'cert-container' ) ;
32+ const loadingIndicator = document . getElementById ( 'loading-indicator' ) ;
33+
34+ // Clear existing certificates to prevent duplicates
35+ if ( certContainer ) {
36+ certContainer . innerHTML = '' ;
37+ }
38+
39+ // Update loading progress
40+ function updateProgress ( ) {
41+ loadedCount ++ ;
42+ const progress = Math . round ( ( loadedCount / totalCerts ) * 100 ) ;
3043
31- // Update loading progress
32- function updateProgress ( ) {
33- loadedCount ++ ;
34- const progress = Math . round ( ( loadedCount / totalCerts ) * 100 ) ;
35-
36- if ( loadingIndicator ) {
37- loadingIndicator . style . width = `${ progress } %` ;
38- loadingIndicator . setAttribute ( 'aria-valuenow' , progress ) ;
39- loadingIndicator . textContent = `${ progress } %` ;
40- }
41-
42- if ( loadedCount === totalCerts ) {
43- // Hide loading bar when complete
44- setTimeout ( ( ) => {
45- document . getElementById ( 'loading-wrapper' ) . classList . add ( 'opacity-0' , 'h-0' , 'mb-0' ) ;
46- } , 500 ) ;
47- }
44+ if ( loadingIndicator ) {
45+ loadingIndicator . style . width = `${ progress } %` ;
46+ loadingIndicator . setAttribute ( 'aria-valuenow' , progress ) ;
47+ loadingIndicator . textContent = `${ progress } %` ;
4848 }
49-
50- // Create certificate card with Tailwind classes
51- function createCertificateCard ( cert ) {
52- return new Promise ( ( resolve ) => {
53- const uuid = crypto . randomUUID ( ) ;
54- const card = document . createElement ( 'div' ) ;
55- card . className = 'cert-card opacity-0 transform translate-y-4 transition-all duration-300 ease-in-out data-category-' + cert . category ;
56- card . dataset . categories = cert . category ;
57- card . id = uuid ;
58-
59- // Add a staggered animation effect
60- setTimeout ( ( ) => {
61- card . classList . remove ( 'opacity-0' , 'translate-y-4' ) ;
62- } , loadedCount * 100 ) ;
63-
64- card . innerHTML = `
65- <div class="bg-card text-card-foreground rounded-lg shadow-sm overflow-hidden">
66- <div class="relative h-64 border overflow-hidden">
67- <iframe class="relative h-full overflow-hidden pointer-events-none border-0" src="${ cert . path } "></iframe>
49+
50+ if ( loadedCount === totalCerts ) {
51+ // Hide loading bar when complete
52+ setTimeout ( ( ) => {
53+ const loadingWrapper = document . getElementById ( 'loading-wrapper' ) ;
54+ if ( loadingWrapper ) {
55+ loadingWrapper . classList . add ( 'opacity-0' , 'h-0' , 'mb-0' ) ;
56+ }
57+ } , 500 ) ;
58+ }
59+ }
6860
61+ // Create certificate card with Tailwind classes
62+ function createCertificateCard ( cert ) {
63+ return new Promise ( ( resolve ) => {
64+ const uuid = crypto . randomUUID ( ) ;
65+ const card = document . createElement ( 'div' ) ;
66+ card . className = 'cert-card opacity-0 transform translate-y-4 transition-all duration-300 ease-in-out data-category-' + cert . category ;
67+ card . dataset . categories = cert . category ;
68+ card . id = uuid ;
69+
70+ // Add a staggered animation effect
71+ setTimeout ( ( ) => {
72+ card . classList . remove ( 'opacity-0' , 'translate-y-4' ) ;
73+ } , loadedCount * 100 ) ;
74+
75+ card . innerHTML = `
76+ <div class="bg-card text-card-foreground rounded-lg shadow-sm overflow-hidden">
77+ <div class="relative h-64 border overflow-hidden">
78+ <iframe class="relative h-full overflow-hidden pointer-events-none border-0" src="${ cert . path } "></iframe>
6979
70- <div class="absolute inset-0 bg-gradient-to-t from-black/70 via-black/0 to-transparent transform translate-y-full transition-transform duration-300 p-4 flex flex-col justify-end">
71- <div class="text-white font-medium text-sm">${ cert . title } </div>
72- </div>
80+ <div class="absolute inset-0 bg-gradient-to-t from-black/70 via-black/0 to-transparent transform translate-y-full transition-transform duration-300 p-4 flex flex-col justify-end">
81+ <div class="text-white font-medium text-sm">${ cert . title } </div>
7382 </div>
74- < div class="p-4 bg-card" >
75- <div class="flex justify-between items-center m-2 ">
76- <span class="text-xs text-muted-foreground"> ${ cert . category } </span >
77- <a href=" ${ cert . path } " target="_blank" class="badge border px-2 py-1 rounded-0 inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 text-primary -foreground hover:bg-secondary/90 h-8 px-4 py-2" >
78- View Full
79- </a>
80- </div >
83+ </ div>
84+ <div class="p-4 bg-card ">
85+ <div class="flex justify-between items-center m-2" >
86+ <span class="text-xs text-muted -foreground"> ${ cert . category } </span >
87+ <a href=" ${ cert . path } " target="_blank" class="badge border px-2 py-1 rounded-0 inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 text-primary-foreground hover:bg-secondary/90 h-8 px-4 py-2">
88+ View Full
89+ </a >
8190 </div>
8291 </div>
83- ` ;
84-
92+ </div>
93+ ` ;
8594
95+ if ( certContainer ) {
8696 certContainer . appendChild ( card ) ;
87-
88- // Simulate loading time
89- setTimeout ( ( ) => {
90- updateProgress ( ) ;
91- resolve ( ) ;
92- } , 100 ) ;
93-
94- // Add hover effect
95- card . addEventListener ( 'mouseenter' , function ( ) {
96- const overlay = this . querySelector ( '.bg-gradient-to-t' ) ;
97- overlay . classList . remove ( 'translate-y-full' ) ;
98- } ) ;
99-
100- card . addEventListener ( 'mouseleave' , function ( ) {
101- const overlay = this . querySelector ( '.bg-gradient-to-t' ) ;
102- overlay . classList . add ( 'translate-y-full' ) ;
103- } ) ;
97+ }
98+
99+ // Simulate loading time
100+ setTimeout ( ( ) => {
101+ updateProgress ( ) ;
102+ resolve ( ) ;
103+ } , 100 ) ;
104+
105+ // Add hover effect
106+ card . addEventListener ( 'mouseenter' , function ( ) {
107+ const overlay = this . querySelector ( '.bg-gradient-to-t' ) ;
108+ if ( overlay ) overlay . classList . remove ( 'translate-y-full' ) ;
104109 } ) ;
105- }
106-
107- // Load all certificates
108- async function loadAllCertificates ( ) {
109- const loadPromises = certificates . map ( cert => createCertificateCard ( cert ) ) ;
110- await Promise . all ( loadPromises ) ;
111- }
112-
113- // Add filter capabilities
114- function setupFilters ( ) {
115- const filterButtons = document . querySelectorAll ( '.filter-btn' ) ;
116110
117- filterButtons . forEach ( btn => {
118- btn . addEventListener ( 'click' , function ( ) {
119- const filter = this . getAttribute ( 'data-filter' ) ;
120-
121- // Filter cards
122- const cards = document . querySelectorAll ( '.cert-card' ) ;
123- cards . forEach ( card => {
124- const categories = card . dataset . categories ;
125-
126- if ( filter === 'all' || categories . includes ( filter ) ) {
127- card . classList . remove ( 'hidden' ) ;
128- } else {
129- card . classList . add ( 'hidden' ) ;
130- }
131- } ) ;
132- } ) ;
111+ card . addEventListener ( 'mouseleave' , function ( ) {
112+ const overlay = this . querySelector ( '.bg-gradient-to-t' ) ;
113+ if ( overlay ) overlay . classList . add ( 'translate-y-full' ) ;
133114 } ) ;
134- }
135-
136- // Initialize
137- loadAllCertificates ( ) . then ( ( ) => {
138- setupFilters ( ) ;
139115 } ) ;
140- } ) ;
116+ }
117+
118+ // Load all certificates
119+ async function loadAllCertificates ( ) {
120+ if ( ! certContainer ) return ;
121+
122+ const loadPromises = certificates . map ( cert => createCertificateCard ( cert ) ) ;
123+ await Promise . all ( loadPromises ) ;
124+ }
125+
126+ // Initialize
127+ loadAllCertificates ( ) ;
128+ } ) ;
0 commit comments