1+ <!DOCTYPE html>
2+ < html lang ="en ">
3+
4+ < head >
5+ < meta charset ="utf-8 " />
6+ < meta name ="viewport " content ="width=device-width, initial-scale=1, shrink-to-fit=no " />
7+ < meta name ="description " content ="Projects by Tran Huu Dat - Web Developer " />
8+ < meta name ="author " content ="Tran Huu Dat " />
9+ < title > Projects - Tran Huu Dat</ title >
10+ <!-- Favicon-->
11+ < link rel ="icon " type ="image/png " href ="assets/favicon.png " />
12+ <!-- Custom Google font-->
13+ < link rel ="preconnect " href ="https://fonts.googleapis.com " />
14+ < link rel ="preconnect " href ="https://fonts.gstatic.com " crossorigin />
15+ < link
16+ href ="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@100;200;300;400;500;600;700;800;900&display=swap "
17+ rel ="stylesheet " />
18+ <!-- Bootstrap icons-->
19+ < link href ="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.1/font/bootstrap-icons.css " rel ="stylesheet " />
20+ <!-- Core theme CSS (includes Bootstrap)-->
21+ < link href ="css/styles.css " rel ="stylesheet " />
22+ < style >
23+ body {
24+ cursor : none;
25+ /* Main custom cursor is handled by JS element */
26+ position : relative;
27+ }
28+
29+ a ,
30+ button ,
31+ input [type = "submit" ],
32+ .form-control {
33+ cursor : url ('assets/hand_blue.png' ), auto;
34+ /* Custom hand cursor for interactive elements */
35+ }
36+
37+ /* Custom Gradient for "Projects" Title */
38+ .text-gradient-purple-pink {
39+ background : -webkit-linear-gradient (315deg , # 8A2BE2 0% , # FF69B4 100% ); /* BlueViolet to HotPink */
40+ background : linear-gradient (135deg , # 8A2BE2 0% , # FF69B4 100% );
41+ -webkit-background-clip : text;
42+ -moz-background-clip : text;
43+ background-clip : text;
44+ -webkit-text-fill-color : transparent;
45+ -moz-text-fill-color : transparent;
46+ text-fill-color : transparent;
47+ }
48+
49+ .project-card {
50+ margin-bottom : 2.5rem ;
51+ position : relative; /* For absolute positioning of the tag */
52+ }
53+
54+ /* E-commerce Dev Tag/Ribbon */
55+ .project-category-tag-wrapper {
56+ position : absolute;
57+ top : 25px ;
58+ left : -5px ; /* Slightly overlap the card edge if card has no border, or adjust if it has border */
59+ z-index : 1 ;
60+ display : flex;
61+ align-items : stretch;
62+ }
63+ .project-category-tag-arrow {
64+ width : 15px ;
65+ background-color : # 0D6EFD ;
66+ clip-path : polygon (100% 0% , 0% 50% , 100% 100% ); /* Triangle pointing left */
67+ margin-right : -1px ; /* Slight overlap to prevent gap */
68+ }
69+ .project-category-tag-text {
70+ background-color : # 0D6EFD ;
71+ color : white;
72+ padding : 7px 12px 7px 10px ;
73+ font-size : 0.8rem ; /* Smaller font for the tag */
74+ font-weight : bold;
75+ border-radius : 0 4px 4px 0 ;
76+ line-height : 1.2 ;
77+ text-transform : uppercase; /* As it appears in the image */
78+ }
79+
80+
81+ .tech-badge {
82+ margin-right : 0.4rem ;
83+ margin-bottom : 0.4rem ;
84+ font-size : 0.80em ; /* Slightly smaller badges */
85+ padding : 0.4em 0.75em ;
86+ border-radius : 10px ; /* More rounded badges like image */
87+ font-weight : 500 ;
88+ color : white; /* Default text color for badges */
89+ border : 1px solid transparent; /* For consistency */
90+ }
91+
92+ .tech-badge .html5 { background-color : # 0D6EFD ; /* Blue */ }
93+ .tech-badge .css3 { background-color : # D63384 ; /* Pink */ }
94+ .tech-badge .bootstrap { background-color : # 0DCAF0 ; color : # 212529 ; /* Cyan, dark text */ }
95+ .tech-badge .javascript { background-color : # FFDA07 ; color : # 212529 ; /* Yellow, dark text */ }
96+ .tech-badge .nodejs { background-color : # 198754 ; /* Green */ }
97+ .tech-badge .mysql { background-color : # DC3545 ; /* Red */ }
98+
99+ .feature-list {
100+ padding-left : 1.2rem ;
101+ font-size : 0.95rem ;
102+ }
103+
104+ .feature-list li {
105+ margin-bottom : 0.3rem ;
106+ }
107+
108+ .project-card .card-body {
109+ padding-top : 2.5rem !important ; /* Default p-5 is 3rem, adjust to slightly less overall */
110+ }
111+ .project-card-content-wrapper {
112+ padding-top : 35px ; /* Space for the tag above the title */
113+ }
114+
115+
116+ /* Cursor styles (same as your original) */
117+ .cursor-element {
118+ position : fixed;
119+ display : flex;
120+ align-items : center;
121+ pointer-events : none;
122+ z-index : 1070 ;
123+ opacity : 0 ;
124+ transition : opacity 0.3s ease-in-out;
125+ }
126+
127+ .cursor-element .visible {
128+ opacity : 1 ;
129+ }
130+
131+ .cursor-element img {
132+ width : 28px ;
133+ height : auto;
134+ margin-right : 8px ;
135+ }
136+
137+ .cursor-element .cursor-text {
138+ background-color : # 0d6efd ;
139+ color : white;
140+ padding : 6px 12px ;
141+ border-radius : 20px ;
142+ font-size : 0.95em ;
143+ box-shadow : 0 3px 7px rgba (0 , 0 , 0 , 0.15 );
144+ font-weight : bold;
145+ opacity : 1 ;
146+ transition : opacity 0.3s ease-in-out;
147+ }
148+
149+ .cursor-element .cursor-text .fade-out {
150+ opacity : 0 ;
151+ }
152+ </ style >
153+
154+ </ head >
155+
156+ < body class ="d-flex flex-column h-100 ">
157+ < main class ="flex-shrink-0 ">
158+ <!-- Navigation-->
159+ < nav class ="navbar navbar-expand-lg navbar-light bg-white py-3 ">
160+ < div class ="container px-5 ">
161+ < a class ="navbar-brand " href ="index.html "> < span class ="fw-bolder text-primary "> Tran Huu Dat</ span > </ a >
162+ < button class ="navbar-toggler " type ="button " data-bs-toggle ="collapse "
163+ data-bs-target ="#navbarSupportedContent " aria-controls ="navbarSupportedContent "
164+ aria-expanded ="false " aria-label ="Toggle navigation "> < span
165+ class ="navbar-toggler-icon "> </ span > </ button >
166+ < div class ="collapse navbar-collapse " id ="navbarSupportedContent ">
167+ < ul class ="navbar-nav ms-auto mb-2 mb-lg-0 small fw-bolder ">
168+ < li class ="nav-item "> < a class ="nav-link " href ="index.html "> Home</ a > </ li >
169+ < li class ="nav-item "> < a class ="nav-link " href ="resume.html "> Resume</ a > </ li >
170+ < li class ="nav-item "> < a class ="nav-link " href ="projects.html "> Projects</ a > </ li >
171+ < li class ="nav-item "> < a class ="nav-link " href ="contact.html "> Contact</ a > </ li >
172+ </ ul >
173+ </ div >
174+ </ div >
175+ </ nav >
176+
177+ <!-- Projects Section -->
178+ < section class ="py-5 ">
179+ < div class ="container px-5 mb-5 ">
180+ < div class ="text-center mb-5 ">
181+ < h1 class ="display-5 fw-bolder mb-0 "> < span class ="text-gradient-purple-pink d-inline "> Projects</ span > </ h1 >
182+ </ div >
183+ < div class ="row gx-5 justify-content-center ">
184+ < div class ="col-lg-11 col-xl-9 col-xxl-8 ">
185+ <!-- Project Card 1: BrickShop -->
186+ < div class ="card overflow-hidden shadow rounded-4 border-0 project-card ">
187+ < div class ="project-category-tag-wrapper ">
188+ < div class ="project-category-tag-arrow "> </ div >
189+ < div class ="project-category-tag-text "> E-commerce Dev</ div >
190+ </ div >
191+ < div class ="card-body p-4 "> <!-- Adjusted padding -->
192+ < div class ="project-card-content-wrapper "> <!-- Wrapper for content below tag -->
193+ < h2 class ="fw-bolder "> BrickShop - Building Block Toy Paradise < span role ="img " aria-label ="brick emoji "> 🧱</ span > </ h2 >
194+ < p class ="text-muted "> Welcome to BrickShop, an e-commerce website project dedicated to providing creative building block toys from leading brands like Qman, Keeppley, and LEGO. Explore a world of creativity with diverse themes and a seamless shopping experience.</ p >
195+
196+ < h5 class ="mt-4 mb-3 "> Technology Stack:</ h5 >
197+ < div class ="mb-4 "> <!-- Wrapper for badges -->
198+ < span class ="badge tech-badge html5 "> HTML5</ span >
199+ < span class ="badge tech-badge css3 "> CSS3</ span >
200+ < span class ="badge tech-badge bootstrap "> Bootstrap</ span >
201+ < span class ="badge tech-badge javascript "> JavaScript</ span >
202+ < span class ="badge tech-badge nodejs "> Node.js</ span >
203+ < span class ="badge tech-badge mysql "> MySQL</ span >
204+ </ div >
205+
206+ < h5 class ="mt-4 "> Key Features Overview:</ h5 >
207+ < div class ="row ">
208+ < div class ="col-md-6 ">
209+ < h6 class ="fw-semibold "> For Customers:</ h6 >
210+ < ul class ="list-unstyled feature-list ">
211+ < li > < i class ="bi bi-check-circle-fill text-primary me-2 "> </ i > Authentication & Account Management</ li >
212+ < li > < i class ="bi bi-check-circle-fill text-primary me-2 "> </ i > Product Browsing, Filtering & Search</ li >
213+ < li > < i class ="bi bi-check-circle-fill text-primary me-2 "> </ i > Detailed Product View & Gallery</ li >
214+ < li > < i class ="bi bi-check-circle-fill text-primary me-2 "> </ i > Cart & Secure Checkout (Vouchers, QR)</ li >
215+ < li > < i class ="bi bi-check-circle-fill text-primary me-2 "> </ i > Order History & Tracking</ li >
216+ </ ul >
217+ </ div >
218+ < div class ="col-md-6 ">
219+ < h6 class ="fw-semibold "> For Administrators:</ h6 >
220+ < ul class ="list-unstyled feature-list ">
221+ < li > < i class ="bi bi-check-circle-fill text-primary me-2 "> </ i > Dashboard with Key Metrics</ li >
222+ < li > < i class ="bi bi-check-circle-fill text-primary me-2 "> </ i > User, Product, Order Management</ li >
223+ < li > < i class ="bi bi-check-circle-fill text-primary me-2 "> </ i > Discount/Coupon & Category Mgmt</ li >
224+ < li > < i class ="bi bi-check-circle-fill text-primary me-2 "> </ i > Multi-language Product Input</ li >
225+ </ ul >
226+ </ div >
227+ </ div >
228+
229+ < p class ="mt-4 ">
230+ < small class ="text-muted "> Admin panel built with Tailwind CSS, customer site with Bootstrap. Full details on GitHub.</ small >
231+ </ p >
232+ < p class ="mt-3 ">
233+ < a class ="btn btn-primary px-4 py-3 me-2 " href ="https://github.com/TranHuuDat2004/BrickShop " target ="_blank " rel ="noopener noreferrer ">
234+ < i class ="bi bi-github "> </ i > View on GitHub
235+ </ a >
236+ </ p >
237+ < p class ="mt-3 "> < small class ="text-muted "> License: CC BY-NC 4.0</ small > </ p >
238+ </ div >
239+ </ div >
240+ </ div >
241+ </ div >
242+ </ div >
243+ </ div >
244+ </ section >
245+ </ main >
246+
247+ <!-- Footer-->
248+ < footer class ="bg-white py-4 mt-auto ">
249+ < div class ="container px-5 ">
250+ < div class ="row align-items-center justify-content-between flex-column flex-sm-row ">
251+ < div class ="col-auto ">
252+ < div class ="small m-0 "> Copyright © Tran Huu Dat 2024</ div >
253+ </ div >
254+ < div class ="col-auto ">
255+ < a class ="small " href ="#! "> Privacy Policy</ a >
256+ < span class ="mx-1 "> ·</ span >
257+ < a class ="small " href ="#! "> Terms of Use</ a >
258+ < span class ="mx-1 "> ·</ span >
259+ < a class ="small " href ="contact.html "> Contact</ a >
260+ </ div >
261+ </ div >
262+ </ div >
263+ </ footer >
264+
265+ <!-- Cursor Element HTML -->
266+ < div class ="cursor-element ">
267+ < img src ="assets/cursor_blue.png " alt ="Custom Cursor Icon ">
268+ < span class ="cursor-text "> Huu Dat</ span >
269+ </ div >
270+
271+ <!-- Bootstrap core JS-->
272+ < script src ="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js "> </ script >
273+ <!-- Core theme JS-->
274+ < script src ="js/scripts.js "> </ script >
275+ <!-- JavaScript cho Interactive Grid và Cursor -->
276+ < script >
277+ const cursorElement = document . querySelector ( '.cursor-element' ) ;
278+ const cursorTextElement = cursorElement . querySelector ( '.cursor-text' ) ;
279+
280+ let autoTextCurrentIndex = 0 ;
281+ let autoTextInterval ;
282+
283+ const autoTexts = [
284+ "My Work" ,
285+ "BrickShop Project" ,
286+ "E-commerce Dev" ,
287+ "Full-Stack Apps" ,
288+ "Explore Further!"
289+ ] ;
290+
291+ function updateCursorPosition ( x , y ) {
292+ if ( ! cursorElement ) return ;
293+ if ( cursorElement . classList . contains ( 'hidden-by-interaction' ) ) {
294+ if ( cursorElement . style . opacity !== '0' ) cursorElement . style . opacity = '0' ;
295+ return ;
296+ }
297+ if ( cursorElement . style . opacity !== '1' && cursorElement . classList . contains ( 'visible' ) ) {
298+ cursorElement . style . opacity = '1' ;
299+ }
300+ cursorElement . style . left = x + 'px' ;
301+ cursorElement . style . top = y + 'px' ;
302+ if ( ! cursorElement . classList . contains ( 'visible' ) ) {
303+ cursorElement . classList . add ( 'visible' ) ;
304+ }
305+ }
306+
307+ function changeCursorTextWithFade ( newText ) {
308+ if ( ! cursorTextElement || cursorTextElement . isTextFading || cursorTextElement . textContent === newText ) {
309+ return ;
310+ }
311+ cursorTextElement . isTextFading = true ;
312+ cursorTextElement . classList . add ( 'fade-out' ) ;
313+
314+ setTimeout ( ( ) => {
315+ cursorTextElement . textContent = newText ;
316+ cursorTextElement . classList . remove ( 'fade-out' ) ;
317+ cursorTextElement . isTextFading = false ;
318+ } , 300 ) ;
319+ }
320+
321+ function updateAutoCursorText ( ) {
322+ if ( cursorElement && cursorElement . classList . contains ( 'hidden-by-interaction' ) ) {
323+ return ;
324+ }
325+ const newTextToShow = autoTexts [ autoTextCurrentIndex ] ;
326+ autoTextCurrentIndex = ( autoTextCurrentIndex + 1 ) % autoTexts . length ;
327+ changeCursorTextWithFade ( newTextToShow ) ;
328+ }
329+
330+ if ( cursorElement ) {
331+ document . addEventListener ( 'mousemove' , ( e ) => {
332+ updateCursorPosition ( e . clientX , e . clientY ) ;
333+ } ) ;
334+
335+ // IMPORTANT: .project-card is REMOVED from this list
336+ // The custom text cursor will NOT hide when hovering over .project-card
337+ const interactiveElements = document . querySelectorAll ( 'a, button, input[type="submit"], .form-control' ) ;
338+
339+ interactiveElements . forEach ( el => {
340+ el . addEventListener ( 'mouseenter' , ( ) => {
341+ cursorElement . classList . add ( 'hidden-by-interaction' ) ;
342+ } ) ;
343+ el . addEventListener ( 'mouseleave' , ( ) => {
344+ cursorElement . classList . remove ( 'hidden-by-interaction' ) ;
345+ } ) ;
346+ } ) ;
347+ }
348+
349+ if ( cursorTextElement ) {
350+ updateAutoCursorText ( ) ;
351+ autoTextInterval = setInterval ( updateAutoCursorText , 3000 ) ;
352+ }
353+
354+ if ( cursorElement ) {
355+ function initialCursorShow ( ) {
356+ if ( ! cursorElement . classList . contains ( 'hidden-by-interaction' ) && ! cursorElement . classList . contains ( 'visible' ) ) {
357+ cursorElement . classList . add ( 'visible' ) ;
358+ }
359+ }
360+ setTimeout ( initialCursorShow , 100 ) ;
361+ } else {
362+ console . error ( "Cursor element not found for initial show!" ) ;
363+ }
364+ </ script >
365+ </ body >
366+ </ html >
0 commit comments