1+ <!DOCTYPE html>
2+ < html lang ="en ">
3+ < head >
4+ < meta charset ="UTF-8 ">
5+ < meta name ="viewport " content ="width=device-width, initial-scale=1.0 ">
6+ < title > Trader Jokes Printer</ title >
7+ <!-- Load Tailwind CSS -->
8+ < script src ="https://cdn.tailwindcss.com "> </ script >
9+ <!-- Use Inter font family -->
10+ < style >
11+ @import url ('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap' );
12+ body {
13+ font-family : 'Inter' , sans-serif;
14+ @apply bg-gray-50;
15+ }
16+ /* Define a CSS variable for dynamic padding, applied by the JS sliders */
17+ # outputContent {
18+ --joke-padding : 24px ; /* Default value */
19+ /* Initial font size is set here */
20+ font-size : 16pt ;
21+ }
22+
23+ /* --- Print Styles (Crucial for 8.5x11 Paper) --- */
24+ @media print {
25+ /* Set the page size and default margins for the paper */
26+ @page {
27+ size : Letter; /* 8.5in x 11in */
28+ margin : 0.75in ;
29+ }
30+
31+ /* Hide all UI elements except the dedicated print area */
32+ .no-print {
33+ display : none !important ;
34+ }
35+
36+ /* Ensure the print area takes up the whole space */
37+ .print-area {
38+ width : 100% !important ;
39+ min-height : 100vh !important ;
40+ margin : 0 !important ;
41+ padding : 0 !important ;
42+ box-shadow : none !important ;
43+ background-color : white !important ;
44+ }
45+
46+ /* The core rule to prevent joke blocks from breaking mid-page */
47+ /* Use the dynamic variable for spacing */
48+ .joke-block {
49+ page-break-inside : avoid;
50+ break-inside : avoid;
51+ margin-bottom : var (--joke-padding ) !important ;
52+ }
53+ }
54+ </ style >
55+ </ head >
56+ < body class ="p-4 md:p-8 ">
57+
58+ < div id ="app " class ="max-w-4xl mx-auto ">
59+
60+ <!-- Header - Non-Printable -->
61+ < header class ="mb-8 p-6 bg-white rounded-xl shadow-lg no-print ">
62+ < h1 class ="text-3xl font-bold text-gray-800 tracking-tight "> Trader Jokes Print Formatter</ h1 >
63+ < p class ="text-gray-500 mt-2 "> Customize the header and formatting below, then paste your joke text.</ p >
64+ </ header >
65+
66+ <!-- Input and Control Area - Non-Printable -->
67+ < div class ="space-y-6 no-print ">
68+
69+ <!-- Custom Header Inputs -->
70+ < div class ="bg-white p-6 rounded-xl shadow-lg grid grid-cols-1 md:grid-cols-3 gap-4 ">
71+ < div >
72+ < label for ="customTitle " class ="block text-sm font-medium text-gray-700 mb-1 "> Print Title (e.g., Nov Show)</ label >
73+ < input type ="text " id ="customTitle " value ="TRADER JOKES " class ="w-full p-2 border border-gray-300 rounded-lg text-gray-800 " />
74+ </ div >
75+ < div >
76+ < label for ="customDate " class ="block text-sm font-medium text-gray-700 mb-1 "> Date</ label >
77+ <!-- Date input will be set to today's date via script -->
78+ < input type ="date " id ="customDate " class ="w-full p-2 border border-gray-300 rounded-lg text-gray-800 " />
79+ </ div >
80+ < div >
81+ < label for ="recipientName " class ="block text-sm font-medium text-gray-700 mb-1 "> Recipient Name (Optional)</ label >
82+ < input type ="text " id ="recipientName " placeholder ="Comedian's Name " class ="w-full p-2 border border-gray-300 rounded-lg text-gray-800 " />
83+ </ div >
84+ </ div >
85+
86+ <!-- Style Controls (Sliders) -->
87+ < div class ="bg-white p-6 rounded-xl shadow-lg space-y-4 ">
88+ < h3 class ="text-lg font-semibold text-gray-800 border-b pb-2 mb-4 "> Print Formatting</ h3 >
89+
90+ <!-- Font Size Control -->
91+ < div >
92+ < label for ="fontSizeSlider " class ="block text-sm font-medium text-gray-700 mb-2 "> Font Size: < span id ="fontSizeValue "> 16</ span > pt</ label >
93+ < input type ="range " id ="fontSizeSlider " min ="12 " max ="24 " value ="16 " step ="2 " oninput ="updatePrintStyles() " class ="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer range-lg " />
94+ </ div >
95+
96+ <!-- Padding Control (Vertical Spacing between jokes) -->
97+ < div >
98+ < label for ="paddingSlider " class ="block text-sm font-medium text-gray-700 mb-2 "> Vertical Joke Spacing: < span id ="paddingValue "> 24</ span > px</ label >
99+ < input type ="range " id ="paddingSlider " min ="12 " max ="60 " value ="24 " step ="6 " oninput ="updatePrintStyles() " class ="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer range-lg " />
100+ </ div >
101+ </ div >
102+
103+ <!-- Input Textarea -->
104+ < div class ="bg-white p-6 rounded-xl shadow-lg ">
105+ < label for ="jokeInput " class ="block text-sm font-medium text-gray-700 mb-2 "> Paste Joke Text Here</ label >
106+ < textarea id ="jokeInput " rows ="15 " oninput ="formatAndRenderPreview() " class ="w-full p-4 border border-gray-300 rounded-lg focus:ring-indigo-500 focus:border-indigo-500 text-gray-800 " placeholder ="Paste all your jokes, even if they have weird spacing or formatting. "> </ textarea >
107+ </ div >
108+
109+ <!-- Action Button -->
110+ < button onclick ="formatAndPrint() " class ="w-full bg-indigo-600 hover:bg-indigo-700 text-white font-semibold py-3 px-6 rounded-xl shadow-lg transition duration-150 ease-in-out transform hover:scale-[1.01] focus:outline-none focus:ring-4 focus:ring-indigo-500 focus:ring-opacity-50 ">
111+ Format & Print Jokes
112+ </ button >
113+ </ div >
114+
115+ <!-- Print Preview Area -->
116+ < div id ="print-area " class ="print-area mt-10 p-8 md:p-12 bg-white rounded-xl shadow-xl transition-all duration-300 ">
117+ < h2 class ="text-xl font-bold text-gray-500 text-center mb-6 no-print "> Print Preview</ h2 >
118+ < div id ="outputContent " class ="text-gray-900 leading-relaxed ">
119+ < p class ="text-center italic text-gray-400 text-lg "> Paste your jokes above and adjust settings to see the final, print-ready output here.</ p >
120+ </ div >
121+ </ div >
122+
123+ </ div >
124+
125+ < script >
126+ // Global references
127+ const jokeInput = document . getElementById ( 'jokeInput' ) ;
128+ const outputContent = document . getElementById ( 'outputContent' ) ;
129+ const customTitleInput = document . getElementById ( 'customTitle' ) ;
130+ const customDateInput = document . getElementById ( 'customDate' ) ;
131+ const recipientNameInput = document . getElementById ( 'recipientName' ) ;
132+ const fontSizeSlider = document . getElementById ( 'fontSizeSlider' ) ;
133+ const paddingSlider = document . getElementById ( 'paddingSlider' ) ;
134+ const fontSizeValue = document . getElementById ( 'fontSizeValue' ) ;
135+ const paddingValue = document . getElementById ( 'paddingValue' ) ;
136+
137+
138+ /**
139+ * Updates the font size and padding styles on the output content for live preview.
140+ */
141+ function updatePrintStyles ( ) {
142+ const fs = fontSizeSlider . value ;
143+ const padding = paddingSlider . value ;
144+
145+ // Update displayed values
146+ fontSizeValue . textContent = fs ;
147+ paddingValue . textContent = padding ;
148+
149+ // Apply styles to the output container for live preview
150+ outputContent . style . fontSize = `${ fs } pt` ;
151+ outputContent . style . setProperty ( '--joke-padding' , `${ padding } px` ) ;
152+
153+ // Re-render the jokes immediately if content exists to update joke-block margins
154+ if ( jokeInput . value . trim ( ) ) {
155+ formatAndRenderPreview ( ) ;
156+ }
157+ }
158+
159+
160+ /**
161+ * Formats the raw text, applies custom header fields, and renders it into the preview area.
162+ */
163+ function formatAndRenderPreview ( ) {
164+ const rawText = jokeInput . value . trim ( ) ;
165+ let formattedHtml = '' ;
166+
167+ // Get custom header values
168+ const title = customTitleInput . value . trim ( ) . toUpperCase ( ) || "TRADER JOKES" ;
169+ // Format the date input if it exists, otherwise use today's date
170+ let dateStr ;
171+ if ( customDateInput . value ) {
172+ dateStr = new Date ( customDateInput . value + 'T00:00:00' ) . toLocaleDateString ( 'en-US' , { year : 'numeric' , month : 'long' , day : 'numeric' } ) ;
173+ } else {
174+ dateStr = new Date ( ) . toLocaleDateString ( 'en-US' , { year : 'numeric' , month : 'long' , day : 'numeric' } ) ;
175+ }
176+ const recipient = recipientNameInput . value . trim ( ) ;
177+
178+
179+ // Process the input text (split by two or more newlines)
180+ const jokeBlocks = rawText
181+ . replace ( / \r \n / g, '\n' ) // Normalize CRLF to LF
182+ . split ( / \n { 2 , } / ) // Split by two or more consecutive newlines
183+ . map ( block => block . trim ( ) ) // Clean up whitespace around each block
184+ . filter ( block => block . length > 0 ) ; // Remove any empty blocks
185+
186+
187+ // Set the print-specific title, date, and recipient (Comedian's Name)
188+ let headerHtml = `
189+ <div class="text-center pb-4 border-b border-gray-300 joke-block" style="margin-bottom: 30px !important;">
190+ <h1 class="font-extrabold mb-1">${ title } </h1>
191+ <p class="font-medium">${ dateStr } </p>
192+ ${ recipient ? `<p class="font-semibold mt-2">${ recipient } </p>` : '' }
193+ </div>
194+ ` ;
195+
196+ if ( jokeBlocks . length > 0 ) {
197+ // Map each joke block to a div with the anti-break class and line breaks converted
198+ formattedHtml = jokeBlocks . map ( block => `
199+ <div class="joke-block leading-relaxed">
200+ ${ block . replace ( / \n / g, '<br>' ) }
201+ </div>
202+ ` ) . join ( '' ) ;
203+ } else {
204+ formattedHtml = '<p class="text-center italic text-gray-400">Paste your jokes above to see the output.</p>' ;
205+ }
206+
207+ outputContent . innerHTML = headerHtml + formattedHtml ;
208+ }
209+
210+ /**
211+ * Cleans, formats, and renders the text for print, then triggers the print dialog.
212+ */
213+ function formatAndPrint ( ) {
214+ if ( ! jokeInput . value . trim ( ) ) {
215+ // Use a custom message box instead of alert()
216+ showUserMessage ( "No Content" , "Please paste your joke text into the box first." , "bg-yellow-500" ) ;
217+ return ;
218+ }
219+
220+ // 1. Ensure latest styles and content are rendered
221+ updatePrintStyles ( ) ; // Apply dynamic styles to outputContent
222+ formatAndRenderPreview ( ) ; // Generate content based on inputs
223+
224+ // 2. Trigger Print
225+ window . print ( ) ;
226+ }
227+
228+ // --- Custom Modal/Message Box (Replaces alert()) ---
229+
230+ function showUserMessage ( title , message , bgColor ) {
231+ const modalId = 'custom-message-modal' ;
232+ let modal = document . getElementById ( modalId ) ;
233+
234+ if ( ! modal ) {
235+ modal = document . createElement ( 'div' ) ;
236+ modal . id = modalId ;
237+ modal . className = 'fixed inset-0 z-50 flex items-center justify-center bg-gray-900 bg-opacity-50 transition-opacity duration-300 opacity-0 pointer-events-none' ;
238+ modal . innerHTML = `
239+ <div class="bg-white rounded-xl shadow-2xl overflow-hidden w-full max-w-sm transform scale-90 transition-transform duration-300">
240+ <div id="modal-header" class="${ bgColor } p-4 text-white">
241+ <h3 class="text-lg font-bold" id="modal-title"></h3>
242+ </div>
243+ <div class="p-6">
244+ <p class="text-gray-700 mb-6" id="modal-message"></p>
245+ <button onclick="document.getElementById('${ modalId } ').classList.add('opacity-0', 'pointer-events-none'); document.getElementById('${ modalId } ').classList.remove('opacity-100');"
246+ class="w-full bg-indigo-600 hover:bg-indigo-700 text-white font-semibold py-2 rounded-lg transition duration-150">
247+ OK
248+ </button>
249+ </div>
250+ </div>
251+ ` ;
252+ document . body . appendChild ( modal ) ;
253+ }
254+
255+ // Update content and show
256+ document . getElementById ( 'modal-title' ) . textContent = title ;
257+ document . getElementById ( 'modal-message' ) . textContent = message ;
258+ document . getElementById ( 'modal-header' ) . className = `${ bgColor } p-4 text-white` ;
259+
260+ // Use requestAnimationFrame to ensure CSS transition works
261+ requestAnimationFrame ( ( ) => {
262+ modal . classList . remove ( 'opacity-0' , 'pointer-events-none' ) ;
263+ modal . classList . add ( 'opacity-100' ) ;
264+ } ) ;
265+ }
266+
267+ // Initialize styles and date on load
268+ window . onload = ( ) => {
269+ updatePrintStyles ( ) ;
270+ // Set the default date to today
271+ customDateInput . valueAsDate = new Date ( ) ;
272+ formatAndRenderPreview ( ) ; // Initial render of the header
273+ } ;
274+ </ script >
275+ </ body >
276+ </ html >
0 commit comments