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.0 ">
7+ < meta http-equiv ="X-UA-Compatible " content ="ie=edge ">
8+ < title > Build : : ModComps</ title >
9+ < link rel ="stylesheet " href ="../css/reset.css ">
10+ < link href ="
https://cdn.jsdelivr.net/npm/[email protected] /dist/css/bootstrap.min.css "
rel ="
stylesheet "
11+ integrity ="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB " crossorigin ="anonymous ">
12+ < link rel ="
stylesheet "
type ="
text/css "
href ="
//cdn.jsdelivr.net/npm/[email protected] /slick/slick.css "
/> 13+ < link rel ="stylesheet " href ="../css/style.css ">
14+ </ head >
15+
16+ < body >
17+ < header id ="site-header ">
18+ < nav class ="navbar navbar-expand-lg bg-body-tertiary ">
19+ < div class ="container ">
20+ < a class ="navbar-brand " href ="/ "> ModComps</ a >
21+ < button class ="navbar-toggler " type ="button " data-bs-toggle ="collapse " data-bs-target ="#navbarNav "
22+ aria-controls ="navbarNav " aria-expanded ="false " aria-label ="Toggle navigation ">
23+ < span class ="navbar-toggler-icon "> </ span >
24+ </ button >
25+ < div class ="collapse navbar-collapse " id ="navbarNav ">
26+ < ul class ="navbar-nav ">
27+ < li class ="nav-item ">
28+ < a class ="nav-link " aria-current ="page " href ="/ "> Home</ a >
29+ </ li >
30+ < li class ="nav-item ">
31+ < a class ="nav-link active " href ="/build "> Build</ a >
32+ </ li >
33+ < li class ="nav-item ">
34+ < a class ="nav-link " href ="/docs "> Docs</ a >
35+ </ li >
36+ < li class ="nav-item ">
37+ < a class ="nav-link " href ="/contact "> Contact</ a >
38+ </ li >
39+ </ ul >
40+ </ div >
41+ </ div >
42+ </ nav >
43+ </ header >
44+ < main id ="site-content ">
45+ < section class ="content library ">
46+ < div class ="row ">
47+ < div class ="col-2 ">
48+ < div id ="category-list " class ="list-group sticky-top ">
49+ < a class ="list-group-item list-group-item-action " href ="#banners "> Banners</ a >
50+ < a class ="list-group-item list-group-item-action " href ="#splits "> Splits</ a >
51+ < a class ="list-group-item list-group-item-action " href ="#pods "> Pods</ a >
52+ < a class ="list-group-item list-group-item-action " href ="#strips "> Strips</ a >
53+ < a class ="list-group-item list-group-item-action " href ="#galleries "> Galleries</ a >
54+ < a class ="list-group-item list-group-item-action " href ="#carousels "> Carousels</ a >
55+ </ div >
56+ </ div >
57+ < div class ="col-8 collection ">
58+ < div data-bs-spy ="scroll " data-bs-target ="#category-list " data-bs-smooth-scroll ="true "
59+ class ="scrollspy-example " tabindex ="0 ">
60+ < ul id ="source-list " class ="row list-unstyled ">
61+ < h2 id ="banners "> Banners</ h2 >
62+ < li class ="col-6 item ">
63+ < img src ="../images/banners.jpg " />
64+ < code >
65+ < h1 > Hello, world!</ h1 >
66+ </ code >
67+ </ li >
68+ < li class ="col-6 item ">
69+ < img src ="../images/banners.jpg " />
70+ < code >
71+ < h1 > Whaddup, world!</ h1 >
72+ </ code >
73+ </ li >
74+ < li class ="col-6 item ">
75+ < img src ="../images/banners.jpg " />
76+ < code >
77+ < h1 > Sup, world!</ h1 >
78+ </ code >
79+ </ li >
80+ < li class ="col-6 item ">
81+ < img src ="../images/banners.jpg " />
82+ < code >
83+ < h1 > Asuh, world!</ h1 >
84+ </ code >
85+ </ li >
86+ < h2 id ="splits "> Splits</ h2 >
87+ < li class ="col-6 item ">
88+ < img src ="../images/splits.jpg " />
89+ </ li >
90+ < li class ="col-6 item ">
91+ < img src ="../images/splits.jpg " />
92+ </ li >
93+ < li class ="col-6 item ">
94+ < img src ="../images/splits.jpg " />
95+ </ li >
96+ < li class ="col-6 item ">
97+ < img src ="../images/splits.jpg " />
98+ </ li >
99+ < h2 id ="pods "> Pods</ h2 >
100+ < li class ="col-6 item ">
101+ < img src ="../images/pods.jpg " />
102+ </ li >
103+ < li class ="col-6 item ">
104+ < img src ="../images/pods.jpg " />
105+ </ li >
106+ < li class ="col-6 item ">
107+ < img src ="../images/pods.jpg " />
108+ </ li >
109+ < li class ="col-6 item ">
110+ < img src ="../images/pods.jpg " />
111+ </ li >
112+ < h2 id ="strips "> Strips</ h2 >
113+ < li class ="col-6 item ">
114+ < img src ="../images/strips.jpg " />
115+ </ li >
116+ < li class ="col-6 item ">
117+ < img src ="../images/strips.jpg " />
118+ </ li >
119+ < li class ="col-6 item ">
120+ < img src ="../images/strips.jpg " />
121+ </ li >
122+ < li class ="col-6 item ">
123+ < img src ="../images/strips.jpg " />
124+ </ li >
125+ < h2 id ="galleries "> Galleries</ h2 >
126+ < li class ="col-6 item ">
127+ < img src ="../images/galleries.jpg " />
128+ </ li >
129+ < li class ="col-6 item ">
130+ < img src ="../images/galleries.jpg " />
131+ </ li >
132+ < li class ="col-6 item ">
133+ < img src ="../images/galleries.jpg " />
134+ </ li >
135+ < li class ="col-6 item ">
136+ < img src ="../images/galleries.jpg " />
137+ </ li >
138+ < h2 id ="carousels "> Carousels</ h2 >
139+ < li class ="col-6 item ">
140+ < img src ="../images/carousels.jpg " />
141+ </ li >
142+ < li class ="col-6 item ">
143+ < img src ="../images/carousels.jpg " />
144+ </ li >
145+ < li class ="col-6 item ">
146+ < img src ="../images/carousels.jpg " />
147+ </ li >
148+ < li class ="col-6 item ">
149+ < img src ="../images/carousels.jpg " />
150+ </ li >
151+ </ ul >
152+ </ div >
153+ </ div >
154+ < div class ="col-2 ">
155+ < nav >
156+ < div class ="nav nav-tabs " id ="nav-tab " role ="tablist ">
157+ < button class ="nav-link active " id ="nav-mockup-tab " data-bs-toggle ="tab " data-bs-target ="#nav-mockup " type ="button "
158+ role ="tab " aria-controls ="nav-mockup " aria-selected ="true "> Mockup</ button >
159+ < button class ="nav-link " id ="nav-code-tab " data-bs-toggle ="tab " data-bs-target ="#nav-code " type ="button "
160+ role ="tab " aria-controls ="nav-code " aria-selected ="false "> View Code</ button >
161+ </ div >
162+ </ nav >
163+ < div class ="tab-content " id ="nav-tabContent ">
164+ < div class ="tab-content " id ="nav-tabContent ">
165+ < div class ="tab-pane fade show active " id ="nav-mockup " role ="tabpanel " aria-labelledby ="nav-mockup-tab " tabindex ="0 ">
166+ < p class ="small "> Click/drag to rearrange your components.</ p >
167+
168+ < button id ="download-mockup-btn " class ="btn btn-primary btn-sm mb-3 ">
169+ Download Mockup (JPG)
170+ </ button >
171+
172+ < ul id ="selected-list " class ="list-unstyled ">
173+ </ ul >
174+ </ div >
175+ < div class ="tab-pane fade " id ="nav-code " role ="tabpanel " aria-labelledby ="nav-code-tab " tabindex ="0 ">
176+ < ul id ="selected-code-list " class ="list-unstyled ">
177+ </ ul >
178+ </ div >
179+ </ div >
180+ </ div >
181+ </ div >
182+ </ div >
183+ </ section >
184+ </ main >
185+
186+ < script src ="
https://cdn.jsdelivr.net/npm/[email protected] /dist/js/bootstrap.bundle.min.js "
187+ integrity ="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI "
188+ crossorigin ="anonymous "> </ script >
189+ < script src ="
https://cdn.jsdelivr.net/npm/[email protected] /dist/html2canvas.min.js "
> </ script > 190+ < script type ="
text/javascript "
src ="
//cdn.jsdelivr.net/npm/[email protected] /slick/slick.min.js "
> </ script > 191+ < script src ="
https://cdn.jsdelivr.net/npm/[email protected] /Sortable.min.js "
> </ script > 192+ < script >
193+ document . addEventListener ( 'DOMContentLoaded' , ( ) => {
194+ const sourceList = document . getElementById ( 'source-list' ) ;
195+ const selectedList = document . getElementById ( 'selected-list' ) ;
196+ const selectedCodeList = document . getElementById ( 'selected-code-list' ) ; // Get the code list
197+
198+ // --- New Download Feature Logic ---
199+ const downloadBtn = document . getElementById ( 'download-mockup-btn' ) ;
200+ // const selectedList = document.getElementById('selected-list');
201+
202+ if ( downloadBtn ) {
203+ downloadBtn . addEventListener ( 'click' , ( ) => {
204+ // Check if the list has any items before attempting to download
205+ if ( selectedList . children . length === 0 ) {
206+ alert ( "The Mockup list is empty. Add some components first!" ) ;
207+ return ;
208+ }
209+
210+ // Options for html2canvas
211+ const options = {
212+ // Ignore the remove buttons from the final image
213+ ignoreElements : ( element ) => {
214+ return element . classList . contains ( 'remove-item' ) ;
215+ } ,
216+ // Use a white background (important for JPEGs)
217+ backgroundColor : '#ffffff' ,
218+ // Enable image cross-origin access if needed (optional)
219+ useCORS : true
220+ } ;
221+
222+ // Convert the list content to a canvas
223+ html2canvas ( selectedList , options ) . then ( canvas => {
224+ // 1. Convert the canvas to a JPEG data URL (quality 0.9)
225+ const imageURL = canvas . toDataURL ( 'image/jpeg' , 0.9 ) ;
226+
227+ // 2. Create a temporary link element for the download
228+ const link = document . createElement ( 'a' ) ;
229+
230+ // 3. Set the download filename and image source
231+ link . download = `mockup-${ new Date ( ) . toISOString ( ) . split ( 'T' ) [ 0 ] } .jpg` ;
232+ link . href = imageURL ;
233+
234+ // 4. Trigger the download and clean up
235+ document . body . appendChild ( link ) ;
236+ link . click ( ) ;
237+ document . body . removeChild ( link ) ;
238+ } ) ;
239+ } ) ;
240+ }
241+
242+ // Initialize SortableJS on the target list to make it draggable
243+ // NOTE: SortableJS is only initialized on the Mockup list.
244+ // The code list will be updated based on the Mockup list's order.
245+ new Sortable ( selectedList , {
246+ animation : 150 ,
247+ ghostClass : 'sortable-ghost' ,
248+ chosenClass : 'sortable-chosen' ,
249+ dragClass : 'sortable-drag' ,
250+
251+ // This 'onEnd' function is the key to keeping the code list mirrored
252+ onEnd : function ( evt ) {
253+ // Get all items in the reordered selectedList
254+ const mockupItems = Array . from ( selectedList . children ) ;
255+
256+ // Reorder the selectedCodeList to match the new order of selectedList
257+ const fragment = document . createDocumentFragment ( ) ;
258+ mockupItems . forEach ( mockupItem => {
259+ // Get the unique ID from the mockup item (added in the 'click' listener)
260+ const uniqueId = mockupItem . dataset . uniqueId ;
261+
262+ // Find the corresponding code item using the unique ID
263+ const codeItem = selectedCodeList . querySelector ( `[data-unique-id="${ uniqueId } "]` ) ;
264+
265+ if ( codeItem ) {
266+ fragment . appendChild ( codeItem ) ;
267+ }
268+ } ) ;
269+ selectedCodeList . appendChild ( fragment ) ;
270+ }
271+ } ) ;
272+
273+ // Variable to generate unique IDs for mirrored elements
274+ let uniqueItemId = 0 ;
275+
276+ // --- Add Item Logic (Mirrors to both lists) ---
277+ sourceList . addEventListener ( 'click' , ( event ) => {
278+ const clickedItem = event . target . closest ( '.item' ) ;
279+ if ( ! clickedItem ) return ;
280+
281+ // 1. Generate a unique ID for the pair of list items
282+ const currentUniqueId = uniqueItemId ++ ;
283+
284+ // --- Mockup List Item ---
285+ const newItemMockup = clickedItem . cloneNode ( true ) ;
286+ newItemMockup . classList . remove ( 'item-selected' ) ;
287+ // Add the unique ID to the mockup item
288+ newItemMockup . dataset . uniqueId = currentUniqueId ;
289+
290+ // Add a visual indicator (like a trash icon) for easy removal
291+ const removeButton = document . createElement ( 'span' ) ;
292+ removeButton . classList . add ( 'remove-item' , 'btn' , 'btn-danger' , 'btn-sm' , 'ms-2' ) ;
293+ removeButton . textContent = 'X' ;
294+ // Append the button to a div/span within the clone that will be visible
295+ const contentContainer = document . createElement ( 'div' ) ;
296+ contentContainer . style . display = 'flex' ;
297+ contentContainer . style . alignItems = 'center' ;
298+ contentContainer . innerHTML = newItemMockup . querySelector ( 'img' ) ? newItemMockup . querySelector ( 'img' ) . outerHTML : '' ;
299+ contentContainer . appendChild ( removeButton ) ;
300+
301+ // Replace the content of the list item
302+ newItemMockup . innerHTML = '' ;
303+ newItemMockup . appendChild ( contentContainer ) ;
304+
305+
306+ // --- Code List Item ---
307+ const newItemCode = document . createElement ( 'li' ) ;
308+ newItemCode . classList . add ( 'item' , 'col-12' ) ; // Use col-12 for full width in the code tab
309+ // Add the unique ID to the code item
310+ newItemCode . dataset . uniqueId = currentUniqueId ;
311+
312+ // Get the HTML code from the original item's <code> tag
313+ const codeElement = clickedItem . querySelector ( 'code' ) ;
314+ if ( codeElement ) {
315+ // Create a new <code> element for the code list
316+ const newCodeBlock = document . createElement ( 'code' ) ;
317+ newCodeBlock . textContent = codeElement . textContent . trim ( ) ;
318+ newCodeBlock . style . display = 'block' ; // Ensure the code block takes up space
319+ newCodeBlock . style . whiteSpace = 'pre' ; // Preserve formatting
320+ newCodeBlock . style . backgroundColor = '#f4f4f4' ;
321+ newCodeBlock . style . padding = '5px' ;
322+ newCodeBlock . style . marginBottom = '10px' ;
323+ newItemCode . appendChild ( newCodeBlock ) ;
324+ } else {
325+ // If no <code> tag, just add a placeholder text
326+ newItemCode . textContent = clickedItem . textContent . trim ( ) ;
327+ }
328+
329+ // 3. Add to both lists
330+ selectedList . appendChild ( newItemMockup ) ;
331+ selectedCodeList . appendChild ( newItemCode ) ;
332+ } ) ;
333+
334+ // --- Remove Item Logic (Mirrors to both lists) ---
335+ selectedList . addEventListener ( 'click' , ( event ) => {
336+ // Check if the actual remove button or the list item itself was clicked
337+ const clickedRemoveButton = event . target . closest ( '.remove-item' ) ;
338+ const clickedMockupItem = event . target . closest ( '.item' ) ;
339+
340+ // Only proceed if a remove button or an item in the *selected list* was clicked
341+ if ( ! clickedMockupItem || ! selectedList . contains ( clickedMockupItem ) ) return ;
342+
343+ // Determine if we should remove the item
344+ let itemToRemove = null ;
345+ if ( clickedRemoveButton ) {
346+ // If the explicit remove button was clicked, use its parent item
347+ itemToRemove = clickedRemoveButton . closest ( '.item' ) ;
348+ } else {
349+ // If the item itself was clicked (and no explicit button), remove it
350+ itemToRemove = clickedMockupItem ;
351+ }
352+
353+ if ( itemToRemove ) {
354+ const uniqueIdToRemove = itemToRemove . dataset . uniqueId ;
355+
356+ // 1. Remove from the Mockup list
357+ itemToRemove . remove ( ) ;
358+
359+ // 2. Find and remove the corresponding item from the Code list using the unique ID
360+ const codeItemToRemove = selectedCodeList . querySelector ( `[data-unique-id="${ uniqueIdToRemove } "]` ) ;
361+ if ( codeItemToRemove ) {
362+ codeItemToRemove . remove ( ) ;
363+ }
364+ // console.log(`Removed pair with ID: ${uniqueIdToRemove}`);
365+ }
366+ } ) ;
367+
368+ } ) ;
369+ </ script >
370+ </ body >
371+
372+ </ html >
0 commit comments