-
-
- 1: Hats
- 2: Space
- 4: Sunglasses
- 5: Boxes
- 7: Ties
- 14: Sinks
- 15: Clothes
-
+
+
+
+
+
Letter of Support — Joseph Fallert Brewery
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Letter Preview
+
+
+
+
Lisa Kersavage, Executive Director
+ New York City Landmarks Preservation Commission
+ 253 Broadway, 11th Floor
+ New York, NY 10007
+
+
Dr. Margaret Herman, Director of Research,
+ New York City Landmarks Preservation Commission
+ 253 Broadway, 11th Floor
+ New York, NY 10007
+
+
RE: Letter of Support for Joseph Fallert Brewery Complex RFE
+
+
Dear Ms. Kersavage and Dr. Herman,
+
+
wishes to express support with the community to designate the Joseph Fallert Brewery Complex (the main factory 56 Meserole Street and the office building on 346 Lorimer Street) as an individual New York City Landmark. This multi-building industrial complex stands as one of the last surviving examples of the Brewer's Row on Meserole Street. It represents an irreplaceable record of the area's German immigrant heritage, industrial evolution and the robust Gilded Age commercial architecture of North Brooklyn.
+
+
Built between 1878 and 1910 through multiple construction phases by John Platte and the architectural firm of F. Wunder and Koch & Wagner, the complex has been described as a red-brick castle distinguished by ornate corbelled brickwork, arched windows and door openings, and a prominent masonry turret that remains visible from the surrounding streets. These features are all characteristic of a late nineteenth century industrial Romanesque and Germanic Rundenbogenstil architecture, favored by German immigrant craftsmen and brewery owners in the area.
+
+
Brooklyn was once home to many breweries that have become increasingly rare, as the majority have been demolished or heavily altered. The Complex retains its exterior massing, masonry, arched openings, turret and general character. By 1894, The Brewing Company's output skyrocketed from 3000 barrels of lager to 64,000 using a method of artificial refrigeration and bottling works. Even after Prohibition, the Company transitioned to post brewery operations such as sodas, a warehouse (1920s) and a furniture store (1930s).
+
+
As of this month, the new owner of the Complex has applied for a demolition permit, with the intention to clear the site for residential development. Once demolished, this structure cannot be recovered. The complex has tremendous potential for adaptive reuse as the previous owner filed plans to convert the space into a mixed-use building while retaining its historic character.
+
+
requests the LPC to designate the Joseph Fallert Brewery Complex as an Individual Landmark and that the Commission move promptly to evaluate this property given the active demolition permit application currently on file.
+
+
+
Sincerely,
+
+
+ Signature will appear here
-
-
-
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+ Step 2
+ Your Signature
+ Draw with your finger or mouse, or upload an image of your signature.
+
+
+ Draw
+ Upload Image
+
+
+
+
+
+
+
Clear signature
+
+
+
+
+
+ Click to upload a signature image
+ PNG, JPG — transparent background works best
+
+
+
+
+
Remove
+
+
+
+
+
+
+
+
+ Generate PDF & Open Email Draft
+
+
+
+ Browser security prevents automatic PDF attachments — the PDF will download to your device automatically. Please attach it to the email draft that opens.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/script.js b/lib/script.js
index e69de29..cf0a242 100644
--- a/lib/script.js
+++ b/lib/script.js
@@ -0,0 +1,335 @@
+/* ── State ─────────────────────────────────────────────── */
+let userName = '';
+let signatureDataURL = '';
+let signatureMode = 'draw'; // 'draw' | 'upload'
+
+/* ── Date helpers ──────────────────────────────────────── */
+const MONTHS = [
+ 'January','February','March','April','May','June',
+ 'July','August','September','October','November','December'
+];
+
+function formatDate(d) {
+ return `${MONTHS[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()}`;
+}
+
+const today = new Date();
+const todayStr = formatDate(today);
+
+/* ── DOM refs ──────────────────────────────────────────── */
+const nameInput = document.getElementById('name-input');
+const slot1 = document.getElementById('slot-1');
+const slot2 = document.getElementById('slot-2');
+const letterDate = document.getElementById('letter-date');
+const sigPreviewLetter = document.getElementById('sig-preview-letter');
+const sigNameDisplay = document.getElementById('sig-name-display');
+const sigDateDisplay = document.getElementById('sig-date-display');
+
+const tabDraw = document.getElementById('tab-draw');
+const tabUpload = document.getElementById('tab-upload');
+const panelDraw = document.getElementById('panel-draw');
+const panelUpload = document.getElementById('panel-upload');
+
+const canvas = document.getElementById('sig-canvas');
+const ctx = canvas.getContext('2d');
+const clearBtn = document.getElementById('clear-btn');
+
+const sigUpload = document.getElementById('sig-upload');
+const uploadLabelArea = document.getElementById('upload-label-area');
+const uploadPreviewWrap= document.getElementById('upload-preview-wrap');
+const uploadPreviewImg = document.getElementById('upload-preview-img');
+const removeUploadBtn = document.getElementById('remove-upload-btn');
+
+const submitBtn = document.getElementById('submit-btn');
+const actionNotice = document.getElementById('action-notice');
+
+/* ── Init ──────────────────────────────────────────────── */
+letterDate.textContent = `Date: ${todayStr}`;
+sigDateDisplay.textContent = todayStr;
+
+/* ── Name input ────────────────────────────────────────── */
+nameInput.addEventListener('input', () => {
+ userName = nameInput.value.trim();
+ const display = userName || '';
+ slot1.textContent = display;
+ slot2.textContent = display;
+ sigNameDisplay.textContent = display;
+ checkReady();
+});
+
+/* ── Tab switching ─────────────────────────────────────── */
+tabDraw.addEventListener('click', () => switchTab('draw'));
+tabUpload.addEventListener('click', () => switchTab('upload'));
+
+function switchTab(mode) {
+ signatureMode = mode;
+ if (mode === 'draw') {
+ tabDraw.classList.add('active');
+ tabDraw.setAttribute('aria-selected', 'true');
+ tabUpload.classList.remove('active');
+ tabUpload.setAttribute('aria-selected', 'false');
+ panelDraw.classList.remove('hidden');
+ panelUpload.classList.add('hidden');
+ // restore draw sig if canvas has content
+ signatureDataURL = canvasIsBlank() ? '' : canvas.toDataURL('image/png');
+ } else {
+ tabUpload.classList.add('active');
+ tabUpload.setAttribute('aria-selected', 'true');
+ tabDraw.classList.remove('active');
+ tabDraw.setAttribute('aria-selected', 'false');
+ panelUpload.classList.remove('hidden');
+ panelDraw.classList.add('hidden');
+ // restore upload sig if image exists
+ signatureDataURL = uploadPreviewImg.src && !uploadPreviewWrap.classList.contains('hidden')
+ ? uploadPreviewImg.src
+ : '';
+ }
+ updateLetterSig();
+ checkReady();
+}
+
+/* ── Canvas drawing ────────────────────────────────────── */
+let drawing = false;
+let lastX = 0, lastY = 0;
+
+function getPos(e) {
+ const rect = canvas.getBoundingClientRect();
+ const scaleX = canvas.width / rect.width;
+ const scaleY = canvas.height / rect.height;
+ if (e.touches) {
+ return {
+ x: (e.touches[0].clientX - rect.left) * scaleX,
+ y: (e.touches[0].clientY - rect.top) * scaleY
+ };
+ }
+ return {
+ x: (e.clientX - rect.left) * scaleX,
+ y: (e.clientY - rect.top) * scaleY
+ };
+}
+
+canvas.addEventListener('pointerdown', (e) => {
+ e.preventDefault();
+ drawing = true;
+ const pos = getPos(e);
+ lastX = pos.x; lastY = pos.y;
+ ctx.beginPath();
+ ctx.moveTo(lastX, lastY);
+});
+
+canvas.addEventListener('pointermove', (e) => {
+ if (!drawing) return;
+ e.preventDefault();
+ const pos = getPos(e);
+ ctx.lineWidth = 2.2;
+ ctx.lineCap = 'round';
+ ctx.lineJoin = 'round';
+ ctx.strokeStyle = '#1a1a18';
+ ctx.lineTo(pos.x, pos.y);
+ ctx.stroke();
+ ctx.beginPath();
+ ctx.moveTo(pos.x, pos.y);
+ lastX = pos.x; lastY = pos.y;
+});
+
+function endDraw() {
+ if (!drawing) return;
+ drawing = false;
+ if (!canvasIsBlank()) {
+ signatureDataURL = canvas.toDataURL('image/png');
+ updateLetterSig();
+ checkReady();
+ }
+}
+
+canvas.addEventListener('pointerup', endDraw);
+canvas.addEventListener('pointerleave', endDraw);
+canvas.addEventListener('pointercancel', endDraw);
+
+function canvasIsBlank() {
+ const data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
+ for (let i = 3; i < data.length; i += 4) {
+ if (data[i] > 0) return false;
+ }
+ return true;
+}
+
+clearBtn.addEventListener('click', () => {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ signatureDataURL = '';
+ updateLetterSig();
+ checkReady();
+});
+
+/* ── Image upload ──────────────────────────────────────── */
+sigUpload.addEventListener('change', (e) => {
+ const file = e.target.files[0];
+ if (!file) return;
+ const reader = new FileReader();
+ reader.onload = (ev) => {
+ const dataURL = ev.target.result;
+ uploadPreviewImg.src = dataURL;
+ uploadPreviewWrap.classList.remove('hidden');
+ uploadLabelArea.classList.add('hidden');
+ signatureDataURL = dataURL;
+ updateLetterSig();
+ checkReady();
+ };
+ reader.readAsDataURL(file);
+});
+
+removeUploadBtn.addEventListener('click', () => {
+ sigUpload.value = '';
+ uploadPreviewImg.src = '';
+ uploadPreviewWrap.classList.add('hidden');
+ uploadLabelArea.classList.remove('hidden');
+ signatureDataURL = '';
+ updateLetterSig();
+ checkReady();
+});
+
+/* ── Live letter signature preview ────────────────────── */
+function updateLetterSig() {
+ // Clear old image if any
+ const existingImg = sigPreviewLetter.querySelector('img');
+ if (existingImg) existingImg.remove();
+
+ const hint = sigPreviewLetter.querySelector('.sig-empty-hint');
+
+ if (signatureDataURL) {
+ const img = document.createElement('img');
+ img.src = signatureDataURL;
+ img.alt = 'Signature';
+ img.style.maxWidth = '100%';
+ img.style.maxHeight = '100%';
+ img.style.objectFit = 'contain';
+ if (hint) hint.style.display = 'none';
+ sigPreviewLetter.appendChild(img);
+ } else {
+ if (hint) hint.style.display = '';
+ }
+}
+
+/* ── Ready check ───────────────────────────────────────── */
+function checkReady() {
+ const ready = userName.length > 0 && signatureDataURL.length > 0;
+ submitBtn.disabled = !ready;
+}
+
+/* ── PDF generation ────────────────────────────────────── */
+submitBtn.addEventListener('click', generateAndSend);
+
+function generateAndSend() {
+ const { jsPDF } = window.jspdf;
+ const doc = new jsPDF({ unit: 'mm', format: 'a4' });
+
+ const marginL = 22;
+ const marginR = 22;
+ const pageW = 210;
+ const contentW = pageW - marginL - marginR;
+ const lineH = 6.5;
+
+ doc.setFont('Times', 'normal');
+
+ let y = 22;
+
+ // Date
+ doc.setFontSize(11);
+ doc.text(`Date: ${todayStr}`, marginL, y);
+ y += lineH * 2;
+
+ // Recipient 1
+ doc.text('Lisa Kersavage, Executive Director', marginL, y); y += lineH;
+ doc.text('New York City Landmarks Preservation Commission', marginL, y); y += lineH;
+ doc.text('253 Broadway, 11th Floor', marginL, y); y += lineH;
+ doc.text('New York, NY 10007', marginL, y); y += lineH * 1.5;
+
+ // Recipient 2
+ doc.text('Dr. Margaret Herman, Director of Research,', marginL, y); y += lineH;
+ doc.text('New York City Landmarks Preservation Commission', marginL, y); y += lineH;
+ doc.text('253 Broadway, 11th Floor', marginL, y); y += lineH;
+ doc.text('New York, NY 10007', marginL, y); y += lineH * 1.5;
+
+ // RE line
+ doc.setFont('Times', 'bold');
+ doc.text('RE: Letter of Support for Joseph Fallert Brewery Complex RFE', marginL, y);
+ doc.setFont('Times', 'normal');
+ y += lineH * 1.5;
+
+ // Salutation
+ doc.text('Dear Ms. Kersavage and Dr. Herman,', marginL, y);
+ y += lineH * 1.5;
+
+ // Helper: wrapped paragraph
+ function addParagraph(text) {
+ const lines = doc.splitTextToSize(text, contentW);
+ doc.text(lines, marginL, y);
+ y += lines.length * lineH + lineH * 0.6;
+ }
+
+ // Paragraph 1
+ addParagraph(
+ `${userName} wishes to express support with the community to designate the Joseph Fallert Brewery Complex (the main factory 56 Meserole Street and the office building on 346 Lorimer Street) as an individual New York City Landmark. This multi-building industrial complex stands as one of the last surviving examples of the Brewer's Row on Meserole Street. It represents an irreplaceable record of the area's German immigrant heritage, industrial evolution and the robust Gilded Age commercial architecture of North Brooklyn.`
+ );
+
+ // Paragraph 2
+ addParagraph(
+ `Built between 1878 and 1910 through multiple construction phases by John Platte and the architectural firm of F. Wunder and Koch & Wagner, the complex has been described as a red-brick castle distinguished by ornate corbelled brickwork, arched windows and door openings, and a prominent masonry turret that remains visible from the surrounding streets. These features are all characteristic of a late nineteenth century industrial Romanesque and Germanic Rundenbogenstil architecture, favored by German immigrant craftsmen and brewery owners in the area.`
+ );
+
+ // Paragraph 3
+ addParagraph(
+ `Brooklyn was once home to many breweries that have become increasingly rare, as the majority have been demolished or heavily altered. The Complex retains its exterior massing, masonry, arched openings, turret and general character. By 1894, The Brewing Company's output skyrocketed from 3000 barrels of lager to 64,000 using a method of artificial refrigeration and bottling works. Even after Prohibition, the Company transitioned to post brewery operations such as sodas, a warehouse (1920s) and a furniture store (1930s).`
+ );
+
+ // Paragraph 4
+ addParagraph(
+ `As of this month, the new owner of the Complex has applied for a demolition permit, with the intention to clear the site for residential development. Once demolished, this structure cannot be recovered. The complex has tremendous potential for adaptive reuse as the previous owner filed plans to convert the space into a mixed-use building while retaining its historic character.`
+ );
+
+ // Paragraph 5
+ addParagraph(
+ `${userName} requests the LPC to designate the Joseph Fallert Brewery Complex as an Individual Landmark and that the Commission move promptly to evaluate this property given the active demolition permit application currently on file.`
+ );
+
+ y += lineH * 0.5;
+
+ // Closing
+ doc.text('Sincerely,', marginL, y);
+ y += lineH * 1.5;
+
+ // Signature image
+ const sigImgH = 22; // mm
+ const sigImgW = 55; // mm
+ try {
+ doc.addImage(signatureDataURL, 'PNG', marginL, y, sigImgW, sigImgH);
+ } catch (err) {
+ // If image fails (e.g. cross-origin), skip gracefully
+ console.warn('Signature image could not be embedded:', err);
+ }
+ y += sigImgH + 1;
+
+ // Signature line
+ doc.setDrawColor(80, 80, 80);
+ doc.line(marginL, y, marginL + sigImgW, y);
+ y += lineH;
+
+ // Name + date
+ doc.setFontSize(10);
+ doc.text(userName, marginL, y);
+ doc.text(todayStr, marginL + sigImgW + 6, y);
+
+ // Save
+ doc.save('letter-of-support-fallert-brewery.pdf');
+
+ // Build mailto URL
+ const subject = encodeURIComponent('Letter of Support for Joseph Fallert Brewery');
+ const body = encodeURIComponent(
+ `Dear Ms. Kersavage and Dr. Herman,\n\nPlease find attached my Letter of Support for the Joseph Fallert Brewery Complex Individual Landmark designation.\n\nSincerely,\n${userName}`
+ );
+ const mailtoHref = `mailto:RFE@lpc.nyc.gov?subject=${subject}&body=${body}`;
+
+ // Show notice with explicit mailto button
+ actionNotice.innerHTML = `✓ PDF downloaded to your device.
Open Email Draft Attach the PDF, then send. `;
+ actionNotice.classList.remove('hidden');
+}
diff --git a/lib/style.css b/lib/style.css
index 72523f6..9e26362 100644
--- a/lib/style.css
+++ b/lib/style.css
@@ -1,46 +1,463 @@
+/* ── Variables ─────────────────────────────────────────── */
+:root {
+ --bg: #f5f4f1;
+ --surface: #ffffff;
+ --border: #e4e2dd;
+ --text: #1a1a18;
+ --text-muted: #6b6b67;
+ --accent: #1d4ed8;
+ --accent-dark: #1e3a8a;
+ --accent-bg: #eff6ff;
+ --red: #dc2626;
+ --radius: 10px;
+ --shadow: 0 1px 4px rgba(0,0,0,.06), 0 4px 16px rgba(0,0,0,.06);
+ --font-sans: 'Inter', system-ui, sans-serif;
+ --font-serif: 'Lora', Georgia, serif;
+}
+
+/* ── Reset / Base ──────────────────────────────────────── */
+*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
+
+html { scroll-behavior: smooth; }
+
body {
- margin: 0;
- font-family: 'Roboto', sans-serif;
+ font-family: var(--font-sans);
+ background: var(--bg);
+ color: var(--text);
+ line-height: 1.6;
+ -webkit-font-smoothing: antialiased;
+}
+
+.sr-only {
+ position: absolute;
+ width: 1px; height: 1px;
+ overflow: hidden;
+ clip: rect(0,0,0,0);
+ white-space: nowrap;
+}
+
+/* ── Header ────────────────────────────────────────────── */
+.site-header {
+ background: var(--text);
+ color: #fff;
+ padding: 40px 24px 36px;
+ text-align: center;
+}
+
+.header-inner {
+ max-width: 700px;
+ margin: 0 auto;
+}
+
+.header-eyebrow {
+ font-size: .75rem;
+ font-weight: 600;
+ letter-spacing: .1em;
+ text-transform: uppercase;
+ color: rgba(255,255,255,.55);
+ margin-bottom: 8px;
+}
+
+.site-header h1 {
+ font-family: var(--font-serif);
+ font-size: clamp(1.75rem, 4vw, 2.5rem);
+ font-weight: 600;
+ letter-spacing: -.01em;
+ line-height: 1.2;
+ margin-bottom: 8px;
+}
+
+.header-subtitle {
+ font-size: .9rem;
+ color: rgba(255,255,255,.65);
+}
+
+/* ── Container ─────────────────────────────────────────── */
+.container {
+ max-width: 760px;
+ margin: 0 auto;
+ padding: 40px 20px 80px;
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+}
+
+/* ── Card ──────────────────────────────────────────────── */
+.card {
+ background: var(--surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ box-shadow: var(--shadow);
+ padding: 32px;
+}
+
+.step-label {
+ display: inline-block;
+ font-size: .7rem;
+ font-weight: 600;
+ letter-spacing: .08em;
+ text-transform: uppercase;
+ color: var(--accent);
+ background: var(--accent-bg);
+ border: 1px solid #bfdbfe;
+ border-radius: 4px;
+ padding: 2px 8px;
+ margin-bottom: 14px;
+}
+
+.card-title {
+ font-size: 1.1rem;
+ font-weight: 600;
+ margin-bottom: 6px;
+}
+
+.card-desc {
+ font-size: .875rem;
+ color: var(--text-muted);
+ margin-bottom: 20px;
}
-h1 {
- background-color: orange;
- color: white;
- text-align: center;
- margin: 0;
- padding: 30px 0;
- box-shadow: 10px 10px 32px -12px rgba(0,0,0,0.75);
+/* ── Text Input ────────────────────────────────────────── */
+.text-input {
+ width: 100%;
+ font-family: var(--font-sans);
+ font-size: 1rem;
+ color: var(--text);
+ background: var(--bg);
+ border: 1.5px solid var(--border);
+ border-radius: 6px;
+ padding: 11px 14px;
+ outline: none;
+ transition: border-color .15s, box-shadow .15s;
}
-main {
- text-align: center;
+.text-input::placeholder { color: var(--text-muted); }
+
+.text-input:focus {
+ border-color: var(--accent);
+ box-shadow: 0 0 0 3px rgba(29,78,216,.12);
+}
+
+/* ── Letter Preview ────────────────────────────────────── */
+.letter-preview {
+ padding: 0;
+ overflow: hidden;
+}
+
+.letter-preview-label {
+ font-size: .7rem;
+ font-weight: 600;
+ letter-spacing: .08em;
+ text-transform: uppercase;
+ color: var(--text-muted);
+ padding: 10px 20px;
+ background: var(--bg);
+ border-bottom: 1px solid var(--border);
+}
+
+.letter-body {
+ font-family: var(--font-serif);
+ font-size: .9rem;
+ line-height: 1.75;
+ color: var(--text);
+ padding: 28px 36px 36px;
+}
+
+.letter-body p {
+ margin-bottom: 1.1em;
+}
+
+.letter-date {
+ margin-bottom: 1.5em;
+ font-style: italic;
+ color: var(--text-muted);
+}
+
+.name-slot {
+ font-weight: 600;
+ color: var(--accent);
+ font-style: normal;
}
-button {
- margin: 50px auto;
- background-color: lightskyblue;
- color: white;
- font-size: 25px;
- border-radius: 20px 20px 20px 20px;
- padding: 10px;
- box-shadow: 10px 10px 32px -12px rgba(0,0,0,0.75);
+.name-slot:empty::before {
+ content: '(Your Name / Organization)';
+ color: #d97706;
+ font-weight: 400;
+ font-style: italic;
}
-input {
- height: 45px;
+/* Signature block in letter */
+.sig-block {
+ margin-top: 2em;
}
-input[type="text"] {
- font-size:25px;
+.sig-closing {
+ margin-bottom: 1em;
}
-img {
- height: 350px;
- width: auto;
- box-shadow: 10px 10px 32px -12px rgba(0,0,0,0.75);
+.sig-row {
+ display: flex;
+ align-items: flex-end;
+ gap: 20px;
}
-ul {
- list-style: none;
- font-size: 14px;
-}
\ No newline at end of file
+.sig-image-area {
+ width: 220px;
+ height: 80px;
+ border-bottom: 1.5px solid var(--text);
+ display: flex;
+ align-items: flex-end;
+ justify-content: center;
+ flex-shrink: 0;
+ position: relative;
+}
+
+.sig-image-area img {
+ max-width: 100%;
+ max-height: 100%;
+ object-fit: contain;
+ display: block;
+}
+
+.sig-empty-hint {
+ font-family: var(--font-sans);
+ font-size: .7rem;
+ color: #d97706;
+ font-style: italic;
+ padding-bottom: 6px;
+}
+
+.sig-text-meta {
+ padding-bottom: 4px;
+ font-family: var(--font-sans);
+}
+
+.sig-name-line {
+ font-size: .875rem;
+ font-weight: 600;
+ min-height: 1.4em;
+}
+
+.sig-date-line {
+ font-size: .8rem;
+ color: var(--text-muted);
+ margin-top: 2px;
+}
+
+/* ── Tabs ──────────────────────────────────────────────── */
+.tab-row {
+ display: flex;
+ gap: 4px;
+ background: var(--bg);
+ border: 1px solid var(--border);
+ border-radius: 7px;
+ padding: 3px;
+ margin-bottom: 20px;
+ width: fit-content;
+}
+
+.tab {
+ font-family: var(--font-sans);
+ font-size: .85rem;
+ font-weight: 500;
+ color: var(--text-muted);
+ background: transparent;
+ border: none;
+ border-radius: 5px;
+ padding: 7px 18px;
+ cursor: pointer;
+ transition: background .15s, color .15s;
+}
+
+.tab:hover { color: var(--text); }
+
+.tab.active {
+ background: var(--surface);
+ color: var(--text);
+ box-shadow: 0 1px 3px rgba(0,0,0,.1);
+}
+
+/* ── Tab Panels ────────────────────────────────────────── */
+.tab-panel { display: block; }
+.tab-panel.hidden { display: none; }
+
+/* ── Canvas ────────────────────────────────────────────── */
+.canvas-wrap {
+ position: relative;
+ border: 1.5px solid var(--border);
+ border-radius: 6px;
+ overflow: hidden;
+ background: #fafafa;
+ margin-bottom: 12px;
+ touch-action: none;
+}
+
+#sig-canvas {
+ display: block;
+ width: 100%;
+ height: auto;
+ cursor: crosshair;
+ touch-action: none;
+}
+
+/* ── Upload Area ───────────────────────────────────────── */
+.upload-label {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+ border: 1.5px dashed var(--border);
+ border-radius: 6px;
+ padding: 36px 24px;
+ cursor: pointer;
+ text-align: center;
+ transition: border-color .15s, background .15s;
+ background: #fafafa;
+}
+
+.upload-label:hover {
+ border-color: var(--accent);
+ background: var(--accent-bg);
+}
+
+.upload-icon {
+ color: var(--text-muted);
+ margin-bottom: 4px;
+}
+
+.upload-text {
+ font-size: .9rem;
+ font-weight: 500;
+ color: var(--text);
+}
+
+.upload-sub {
+ font-size: .78rem;
+ color: var(--text-muted);
+}
+
+.upload-preview {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 12px;
+}
+
+.upload-preview.hidden { display: none; }
+
+.upload-preview img {
+ max-height: 120px;
+ max-width: 100%;
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ padding: 8px;
+ background: #fff;
+}
+
+/* ── Ghost Button ──────────────────────────────────────── */
+.ghost-btn {
+ font-family: var(--font-sans);
+ font-size: .8rem;
+ font-weight: 500;
+ color: var(--text-muted);
+ background: transparent;
+ border: 1px solid var(--border);
+ border-radius: 5px;
+ padding: 5px 14px;
+ cursor: pointer;
+ transition: color .15s, border-color .15s;
+}
+
+.ghost-btn:hover {
+ color: var(--red);
+ border-color: var(--red);
+}
+
+/* ── Action Area ───────────────────────────────────────── */
+.action-area {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 14px;
+ padding: 8px 0;
+ text-align: center;
+}
+
+.submit-btn {
+ display: inline-flex;
+ align-items: center;
+ gap: 10px;
+ font-family: var(--font-sans);
+ font-size: 1rem;
+ font-weight: 600;
+ color: #fff;
+ background: var(--accent);
+ border: none;
+ border-radius: 8px;
+ padding: 14px 28px;
+ cursor: pointer;
+ transition: background .15s, transform .1s, box-shadow .15s;
+ box-shadow: 0 2px 8px rgba(29,78,216,.25);
+}
+
+.submit-btn:hover:not(:disabled) {
+ background: var(--accent-dark);
+ transform: translateY(-1px);
+ box-shadow: 0 4px 14px rgba(29,78,216,.35);
+}
+
+.submit-btn:active:not(:disabled) {
+ transform: translateY(0);
+}
+
+.submit-btn:disabled {
+ background: #cbd5e1;
+ color: #94a3b8;
+ cursor: not-allowed;
+ box-shadow: none;
+}
+
+.action-notice {
+ font-size: .875rem;
+ font-weight: 500;
+ color: #15803d;
+ background: #f0fdf4;
+ border: 1px solid #bbf7d0;
+ border-radius: 6px;
+ padding: 10px 18px;
+}
+
+.action-notice.hidden { display: none; }
+
+.attach-hint {
+ font-size: .78rem;
+ color: var(--text-muted);
+ max-width: 480px;
+ line-height: 1.5;
+}
+
+/* ── Footer ────────────────────────────────────────────── */
+.site-footer {
+ text-align: center;
+ padding: 20px;
+ font-size: .8rem;
+ color: var(--text-muted);
+ border-top: 1px solid var(--border);
+}
+
+.site-footer a {
+ color: var(--accent);
+ text-decoration: none;
+}
+
+.site-footer a:hover { text-decoration: underline; }
+
+/* ── Responsive ────────────────────────────────────────── */
+@media (max-width: 600px) {
+ .card { padding: 22px 18px; }
+ .letter-body { padding: 20px 20px 28px; font-size: .85rem; }
+ .sig-row { flex-direction: column; align-items: flex-start; }
+ .sig-image-area { width: 100%; }
+ .submit-btn { width: 100%; justify-content: center; }
+}
diff --git a/script.js b/script.js
new file mode 100644
index 0000000..b6b67f7
--- /dev/null
+++ b/script.js
@@ -0,0 +1,340 @@
+/* ── State ─────────────────────────────────────────────── */
+let userName = '';
+let signatureDataURL = '';
+let signatureMode = 'draw'; // 'draw' | 'upload'
+
+/* ── Date helpers ──────────────────────────────────────── */
+const MONTHS = [
+ 'January','February','March','April','May','June',
+ 'July','August','September','October','November','December'
+];
+
+function formatDate(d) {
+ return `${MONTHS[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()}`;
+}
+
+const today = new Date();
+const todayStr = formatDate(today);
+
+/* ── DOM refs ──────────────────────────────────────────── */
+const nameInput = document.getElementById('name-input');
+const slot1 = document.getElementById('slot-1');
+const slot2 = document.getElementById('slot-2');
+const letterDate = document.getElementById('letter-date');
+const sigPreviewLetter = document.getElementById('sig-preview-letter');
+const sigNameDisplay = document.getElementById('sig-name-display');
+const sigDateDisplay = document.getElementById('sig-date-display');
+
+const tabDraw = document.getElementById('tab-draw');
+const tabUpload = document.getElementById('tab-upload');
+const panelDraw = document.getElementById('panel-draw');
+const panelUpload = document.getElementById('panel-upload');
+
+const canvas = document.getElementById('sig-canvas');
+const ctx = canvas.getContext('2d');
+const clearBtn = document.getElementById('clear-btn');
+
+const sigUpload = document.getElementById('sig-upload');
+const uploadLabelArea = document.getElementById('upload-label-area');
+const uploadPreviewWrap= document.getElementById('upload-preview-wrap');
+const uploadPreviewImg = document.getElementById('upload-preview-img');
+const removeUploadBtn = document.getElementById('remove-upload-btn');
+
+const submitBtn = document.getElementById('submit-btn');
+const actionNotice = document.getElementById('action-notice');
+
+/* ── Init ──────────────────────────────────────────────── */
+letterDate.textContent = `Date: ${todayStr}`;
+sigDateDisplay.textContent = todayStr;
+
+/* ── Name input ────────────────────────────────────────── */
+nameInput.addEventListener('input', () => {
+ userName = nameInput.value.trim();
+ const display = userName || '';
+ slot1.textContent = display;
+ slot2.textContent = display;
+ sigNameDisplay.textContent = display;
+ checkReady();
+});
+
+/* ── Tab switching ─────────────────────────────────────── */
+tabDraw.addEventListener('click', () => switchTab('draw'));
+tabUpload.addEventListener('click', () => switchTab('upload'));
+
+function switchTab(mode) {
+ signatureMode = mode;
+ if (mode === 'draw') {
+ tabDraw.classList.add('active');
+ tabDraw.setAttribute('aria-selected', 'true');
+ tabUpload.classList.remove('active');
+ tabUpload.setAttribute('aria-selected', 'false');
+ panelDraw.classList.remove('hidden');
+ panelUpload.classList.add('hidden');
+ // restore draw sig if canvas has content
+ signatureDataURL = canvasIsBlank() ? '' : canvas.toDataURL('image/png');
+ } else {
+ tabUpload.classList.add('active');
+ tabUpload.setAttribute('aria-selected', 'true');
+ tabDraw.classList.remove('active');
+ tabDraw.setAttribute('aria-selected', 'false');
+ panelUpload.classList.remove('hidden');
+ panelDraw.classList.add('hidden');
+ // restore upload sig if image exists
+ signatureDataURL = uploadPreviewImg.src && !uploadPreviewWrap.classList.contains('hidden')
+ ? uploadPreviewImg.src
+ : '';
+ }
+ updateLetterSig();
+ checkReady();
+}
+
+/* ── Canvas drawing ────────────────────────────────────── */
+let drawing = false;
+let lastX = 0, lastY = 0;
+
+function getPos(e) {
+ const rect = canvas.getBoundingClientRect();
+ const scaleX = canvas.width / rect.width;
+ const scaleY = canvas.height / rect.height;
+ if (e.touches) {
+ return {
+ x: (e.touches[0].clientX - rect.left) * scaleX,
+ y: (e.touches[0].clientY - rect.top) * scaleY
+ };
+ }
+ return {
+ x: (e.clientX - rect.left) * scaleX,
+ y: (e.clientY - rect.top) * scaleY
+ };
+}
+
+canvas.addEventListener('pointerdown', (e) => {
+ e.preventDefault();
+ drawing = true;
+ const pos = getPos(e);
+ lastX = pos.x; lastY = pos.y;
+ ctx.beginPath();
+ ctx.moveTo(lastX, lastY);
+});
+
+canvas.addEventListener('pointermove', (e) => {
+ if (!drawing) return;
+ e.preventDefault();
+ const pos = getPos(e);
+ ctx.lineWidth = 2.2;
+ ctx.lineCap = 'round';
+ ctx.lineJoin = 'round';
+ ctx.strokeStyle = '#1a1a18';
+ ctx.lineTo(pos.x, pos.y);
+ ctx.stroke();
+ ctx.beginPath();
+ ctx.moveTo(pos.x, pos.y);
+ lastX = pos.x; lastY = pos.y;
+});
+
+function endDraw() {
+ if (!drawing) return;
+ drawing = false;
+ if (!canvasIsBlank()) {
+ signatureDataURL = canvas.toDataURL('image/png');
+ updateLetterSig();
+ checkReady();
+ }
+}
+
+canvas.addEventListener('pointerup', endDraw);
+canvas.addEventListener('pointerleave', endDraw);
+canvas.addEventListener('pointercancel', endDraw);
+
+function canvasIsBlank() {
+ const data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
+ for (let i = 3; i < data.length; i += 4) {
+ if (data[i] > 0) return false;
+ }
+ return true;
+}
+
+clearBtn.addEventListener('click', () => {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ signatureDataURL = '';
+ updateLetterSig();
+ checkReady();
+});
+
+/* ── Image upload ──────────────────────────────────────── */
+sigUpload.addEventListener('change', (e) => {
+ const file = e.target.files[0];
+ if (!file) return;
+ const reader = new FileReader();
+ reader.onload = (ev) => {
+ const dataURL = ev.target.result;
+ uploadPreviewImg.src = dataURL;
+ uploadPreviewWrap.classList.remove('hidden');
+ uploadLabelArea.classList.add('hidden');
+ signatureDataURL = dataURL;
+ updateLetterSig();
+ checkReady();
+ };
+ reader.readAsDataURL(file);
+});
+
+removeUploadBtn.addEventListener('click', () => {
+ sigUpload.value = '';
+ uploadPreviewImg.src = '';
+ uploadPreviewWrap.classList.add('hidden');
+ uploadLabelArea.classList.remove('hidden');
+ signatureDataURL = '';
+ updateLetterSig();
+ checkReady();
+});
+
+/* ── Live letter signature preview ────────────────────── */
+function updateLetterSig() {
+ // Clear old image if any
+ const existingImg = sigPreviewLetter.querySelector('img');
+ if (existingImg) existingImg.remove();
+
+ const hint = sigPreviewLetter.querySelector('.sig-empty-hint');
+
+ if (signatureDataURL) {
+ const img = document.createElement('img');
+ img.src = signatureDataURL;
+ img.alt = 'Signature';
+ img.style.maxWidth = '100%';
+ img.style.maxHeight = '100%';
+ img.style.objectFit = 'contain';
+ if (hint) hint.style.display = 'none';
+ sigPreviewLetter.appendChild(img);
+ } else {
+ if (hint) hint.style.display = '';
+ }
+}
+
+/* ── Ready check ───────────────────────────────────────── */
+function checkReady() {
+ const ready = userName.length > 0 && signatureDataURL.length > 0;
+ submitBtn.disabled = !ready;
+}
+
+/* ── PDF generation ────────────────────────────────────── */
+submitBtn.addEventListener('click', generateAndSend);
+
+function generateAndSend() {
+ const { jsPDF } = window.jspdf;
+ const doc = new jsPDF({ unit: 'mm', format: 'a4' });
+
+ const marginL = 22;
+ const marginR = 22;
+ const pageW = 210;
+ const contentW = pageW - marginL - marginR;
+ const lineH = 6.5;
+
+ doc.setFont('Times', 'normal');
+
+ let y = 22;
+
+ // Date
+ doc.setFontSize(11);
+ doc.text(`Date: ${todayStr}`, marginL, y);
+ y += lineH * 2;
+
+ // Recipient 1
+ doc.text('Lisa Kersavage, Executive Director', marginL, y); y += lineH;
+ doc.text('New York City Landmarks Preservation Commission', marginL, y); y += lineH;
+ doc.text('253 Broadway, 11th Floor', marginL, y); y += lineH;
+ doc.text('New York, NY 10007', marginL, y); y += lineH * 1.5;
+
+ // Recipient 2
+ doc.text('Dr. Margaret Herman, Director of Research,', marginL, y); y += lineH;
+ doc.text('New York City Landmarks Preservation Commission', marginL, y); y += lineH;
+ doc.text('253 Broadway, 11th Floor', marginL, y); y += lineH;
+ doc.text('New York, NY 10007', marginL, y); y += lineH * 1.5;
+
+ // RE line
+ doc.setFont('Times', 'bold');
+ doc.text('RE: Letter of Support for Joseph Fallert Brewery Complex RFE', marginL, y);
+ doc.setFont('Times', 'normal');
+ y += lineH * 1.5;
+
+ // Salutation
+ doc.text('Dear Ms. Kersavage and Dr. Herman,', marginL, y);
+ y += lineH * 1.5;
+
+ const pageH = 297;
+ const marginB = 25;
+
+ function checkPageBreak(neededH) {
+ if (y + neededH > pageH - marginB) {
+ doc.addPage();
+ y = 22;
+ }
+ }
+
+ // Helper: wrapped paragraph
+ function addParagraph(text) {
+ const lines = doc.splitTextToSize(text, contentW);
+ checkPageBreak(lines.length * lineH + lineH * 0.6);
+ doc.text(lines, marginL, y);
+ y += lines.length * lineH + lineH * 0.6;
+ }
+
+ // Paragraph 1
+ addParagraph(
+ `${userName} wishes to express support with the community to designate the Joseph Fallert Brewery Complex (the main factory 56 Meserole Street and the office building on 346 Lorimer Street) as an individual New York City Landmark. This multi-building industrial complex stands as one of the last surviving examples of the Brewer's Row on Meserole Street. It represents an irreplaceable record of the area's German immigrant heritage, industrial evolution and the robust Gilded Age commercial architecture of North Brooklyn.`
+ );
+
+ // Paragraph 2
+ addParagraph(
+ `Built between 1878 and 1910 through multiple construction phases by John Platte and the architectural firm of F. Wunder and Koch & Wagner, the complex has been described as a red-brick castle distinguished by ornate corbelled brickwork, arched windows and door openings, and a prominent masonry turret that remains visible from the surrounding streets. These features are all characteristic of a late nineteenth century industrial Romanesque and Germanic Rundenbogenstil architecture, favored by German immigrant craftsmen and brewery owners in the area.`
+ );
+
+ // Paragraph 3
+ addParagraph(
+ `Brooklyn was once home to many breweries that have become increasingly rare, as the majority have been demolished or heavily altered. The Complex retains its exterior massing, masonry, arched openings, turret and general character. By 1894, The Brewing Company's output skyrocketed from 3000 barrels of lager to 64,000 using a method of artificial refrigeration and bottling works. Even after Prohibition, the Company transitioned to post brewery operations such as sodas, a warehouse (1920s) and a furniture store (1930s).`
+ );
+
+ // Paragraph 4
+ addParagraph(
+ `As of this month, the new owner of the Complex has applied for a demolition permit, with the intention to clear the site for residential development. Once demolished, this structure cannot be recovered. The complex has tremendous potential for adaptive reuse as the previous owner filed plans to convert the space into a mixed-use building while retaining its historic character.`
+ );
+
+ // Paragraph 5
+ addParagraph(
+ `${userName} requests the LPC to designate the Joseph Fallert Brewery Complex as an Individual Landmark and that the Commission move promptly to evaluate this property given the active demolition permit application currently on file.`
+ );
+
+ y += lineH * 0.5;
+
+ // Closing
+ checkPageBreak(lineH * 1.5 + 22 + 1 + lineH * 2);
+ doc.text('Sincerely,', marginL, y);
+ y += lineH * 1.5;
+
+ // Signature image
+ const sigImgH = 22; // mm
+ const sigImgW = 55; // mm
+ try {
+ doc.addImage(signatureDataURL, 'PNG', marginL, y, sigImgW, sigImgH);
+ } catch (err) {
+ // If image fails (e.g. cross-origin), skip gracefully
+ console.warn('Signature image could not be embedded:', err);
+ }
+ y += sigImgH + 1;
+
+ // Signature line
+ doc.setDrawColor(80, 80, 80);
+ doc.line(marginL, y, marginL + sigImgW, y);
+ y += lineH;
+
+ // Name + date
+ doc.setFontSize(10);
+ doc.text(userName, marginL, y);
+ doc.text(todayStr, marginL + sigImgW + 6, y);
+
+ // Save
+ doc.save('letter-of-support-fallert-brewery.pdf');
+
+ // Show success notice
+ actionNotice.textContent = '✓ PDF downloaded to your device. Please email it to RFE@lpc.nyc.gov.';
+ actionNotice.classList.remove('hidden');
+}
diff --git a/style.css b/style.css
new file mode 100644
index 0000000..2e4fed9
--- /dev/null
+++ b/style.css
@@ -0,0 +1,404 @@
+/* ── Variables ─────────────────────────────────────────── */
+:root {
+ --bg: #f5f4f1;
+ --surface: #ffffff;
+ --border: #e4e2dd;
+ --text: #1a1a18;
+ --text-muted: #6b6b67;
+ --accent: #1d4ed8;
+ --accent-dark: #1e3a8a;
+ --accent-bg: #eff6ff;
+ --red: #dc2626;
+ --radius: 10px;
+ --shadow: 0 1px 4px rgba(0,0,0,.06), 0 4px 16px rgba(0,0,0,.06);
+ --font-sans: 'Inter', system-ui, sans-serif;
+ --font-serif: 'Lora', Georgia, serif;
+}
+
+/* ── Reset / Base ──────────────────────────────────────── */
+*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
+
+html { scroll-behavior: smooth; }
+
+body {
+ font-family: var(--font-sans);
+ background: var(--bg);
+ color: var(--text);
+ line-height: 1.6;
+ -webkit-font-smoothing: antialiased;
+}
+
+.sr-only {
+ position: absolute;
+ width: 1px; height: 1px;
+ overflow: hidden;
+ clip: rect(0,0,0,0);
+ white-space: nowrap;
+}
+
+/* ── Container ─────────────────────────────────────────── */
+.container {
+ max-width: 760px;
+ margin: 0 auto;
+ padding: 40px 20px 80px;
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+}
+
+/* ── Card ──────────────────────────────────────────────── */
+.card {
+ background: var(--surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ box-shadow: var(--shadow);
+ padding: 32px;
+}
+
+.step-label {
+ display: inline-block;
+ font-size: .7rem;
+ font-weight: 600;
+ letter-spacing: .08em;
+ text-transform: uppercase;
+ color: var(--accent);
+ background: var(--accent-bg);
+ border: 1px solid #bfdbfe;
+ border-radius: 4px;
+ padding: 2px 8px;
+ margin-bottom: 14px;
+}
+
+.card-title {
+ font-size: 1.1rem;
+ font-weight: 600;
+ margin-bottom: 6px;
+}
+
+.card-desc {
+ font-size: .875rem;
+ color: var(--text-muted);
+ margin-bottom: 20px;
+}
+
+/* ── Text Input ────────────────────────────────────────── */
+.text-input {
+ width: 100%;
+ font-family: var(--font-sans);
+ font-size: 1rem;
+ color: var(--text);
+ background: var(--bg);
+ border: 1.5px solid var(--border);
+ border-radius: 6px;
+ padding: 11px 14px;
+ outline: none;
+ transition: border-color .15s, box-shadow .15s;
+}
+
+.text-input::placeholder { color: var(--text-muted); }
+
+.text-input:focus {
+ border-color: var(--accent);
+ box-shadow: 0 0 0 3px rgba(29,78,216,.12);
+}
+
+/* ── Letter Preview ────────────────────────────────────── */
+.letter-preview {
+ padding: 0;
+ overflow: hidden;
+}
+
+.letter-preview-label {
+ font-size: .7rem;
+ font-weight: 600;
+ letter-spacing: .08em;
+ text-transform: uppercase;
+ color: var(--text-muted);
+ padding: 10px 20px;
+ background: var(--bg);
+ border-bottom: 1px solid var(--border);
+}
+
+.letter-body {
+ font-family: var(--font-serif);
+ font-size: .9rem;
+ line-height: 1.75;
+ color: var(--text);
+ padding: 28px 36px 36px;
+}
+
+.letter-body p {
+ margin-bottom: 1.1em;
+}
+
+.letter-date {
+ margin-bottom: 1.5em;
+ font-style: italic;
+ color: var(--text-muted);
+}
+
+.name-slot {
+ font-weight: 600;
+ color: var(--accent);
+ font-style: normal;
+}
+
+.name-slot:empty::before {
+ content: '(Your Name / Organization)';
+ color: #d97706;
+ font-weight: 400;
+ font-style: italic;
+}
+
+/* Signature block in letter */
+.sig-block {
+ margin-top: 2em;
+}
+
+.sig-closing {
+ margin-bottom: 1em;
+}
+
+.sig-row {
+ display: flex;
+ align-items: flex-end;
+ gap: 20px;
+}
+
+.sig-image-area {
+ width: 220px;
+ height: 80px;
+ border-bottom: 1.5px solid var(--text);
+ display: flex;
+ align-items: flex-end;
+ justify-content: center;
+ flex-shrink: 0;
+ position: relative;
+}
+
+.sig-image-area img {
+ max-width: 100%;
+ max-height: 100%;
+ object-fit: contain;
+ display: block;
+}
+
+.sig-empty-hint {
+ font-family: var(--font-sans);
+ font-size: .7rem;
+ color: #d97706;
+ font-style: italic;
+ padding-bottom: 6px;
+}
+
+.sig-text-meta {
+ padding-bottom: 4px;
+ font-family: var(--font-sans);
+}
+
+.sig-name-line {
+ font-size: .875rem;
+ font-weight: 600;
+ min-height: 1.4em;
+}
+
+.sig-date-line {
+ font-size: .8rem;
+ color: var(--text-muted);
+ margin-top: 2px;
+}
+
+/* ── Tabs ──────────────────────────────────────────────── */
+.tab-row {
+ display: flex;
+ gap: 4px;
+ background: var(--bg);
+ border: 1px solid var(--border);
+ border-radius: 7px;
+ padding: 3px;
+ margin-bottom: 20px;
+ width: fit-content;
+}
+
+.tab {
+ font-family: var(--font-sans);
+ font-size: .85rem;
+ font-weight: 500;
+ color: var(--text-muted);
+ background: transparent;
+ border: none;
+ border-radius: 5px;
+ padding: 7px 18px;
+ cursor: pointer;
+ transition: background .15s, color .15s;
+}
+
+.tab:hover { color: var(--text); }
+
+.tab.active {
+ background: var(--surface);
+ color: var(--text);
+ box-shadow: 0 1px 3px rgba(0,0,0,.1);
+}
+
+/* ── Tab Panels ────────────────────────────────────────── */
+.tab-panel { display: block; }
+.tab-panel.hidden { display: none; }
+
+/* ── Canvas ────────────────────────────────────────────── */
+.canvas-wrap {
+ position: relative;
+ border: 1.5px solid var(--border);
+ border-radius: 6px;
+ overflow: hidden;
+ background: #fafafa;
+ margin-bottom: 12px;
+ touch-action: none;
+}
+
+#sig-canvas {
+ display: block;
+ width: 100%;
+ height: auto;
+ cursor: crosshair;
+ touch-action: none;
+}
+
+/* ── Upload Area ───────────────────────────────────────── */
+.upload-label {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+ border: 1.5px dashed var(--border);
+ border-radius: 6px;
+ padding: 36px 24px;
+ cursor: pointer;
+ text-align: center;
+ transition: border-color .15s, background .15s;
+ background: #fafafa;
+}
+
+.upload-label:hover {
+ border-color: var(--accent);
+ background: var(--accent-bg);
+}
+
+.upload-icon {
+ color: var(--text-muted);
+ margin-bottom: 4px;
+}
+
+.upload-text {
+ font-size: .9rem;
+ font-weight: 500;
+ color: var(--text);
+}
+
+.upload-sub {
+ font-size: .78rem;
+ color: var(--text-muted);
+}
+
+.upload-preview {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 12px;
+}
+
+.upload-preview.hidden { display: none; }
+
+.upload-preview img {
+ max-height: 120px;
+ max-width: 100%;
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ padding: 8px;
+ background: #fff;
+}
+
+/* ── Ghost Button ──────────────────────────────────────── */
+.ghost-btn {
+ font-family: var(--font-sans);
+ font-size: .8rem;
+ font-weight: 500;
+ color: var(--text-muted);
+ background: transparent;
+ border: 1px solid var(--border);
+ border-radius: 5px;
+ padding: 5px 14px;
+ cursor: pointer;
+ transition: color .15s, border-color .15s;
+}
+
+.ghost-btn:hover {
+ color: var(--red);
+ border-color: var(--red);
+}
+
+/* ── Action Area ───────────────────────────────────────── */
+.action-area {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 14px;
+ padding: 8px 0;
+ text-align: center;
+}
+
+.submit-btn {
+ display: inline-flex;
+ align-items: center;
+ gap: 10px;
+ font-family: var(--font-sans);
+ font-size: 1rem;
+ font-weight: 600;
+ color: #fff;
+ background: var(--accent);
+ border: none;
+ border-radius: 8px;
+ padding: 14px 28px;
+ cursor: pointer;
+ transition: background .15s, transform .1s, box-shadow .15s;
+ box-shadow: 0 2px 8px rgba(29,78,216,.25);
+}
+
+.submit-btn:hover:not(:disabled) {
+ background: var(--accent-dark);
+ transform: translateY(-1px);
+ box-shadow: 0 4px 14px rgba(29,78,216,.35);
+}
+
+.submit-btn:active:not(:disabled) {
+ transform: translateY(0);
+}
+
+.submit-btn:disabled {
+ background: #cbd5e1;
+ color: #94a3b8;
+ cursor: not-allowed;
+ box-shadow: none;
+}
+
+.action-notice {
+ font-size: .875rem;
+ font-weight: 500;
+ color: #15803d;
+ background: #f0fdf4;
+ border: 1px solid #bbf7d0;
+ border-radius: 6px;
+ padding: 10px 18px;
+}
+
+.action-notice.hidden { display: none; }
+
+/* ── Responsive ────────────────────────────────────────── */
+@media (max-width: 600px) {
+ .card { padding: 22px 18px; }
+ .letter-body { padding: 20px 20px 28px; font-size: .85rem; }
+ .sig-row { flex-direction: column; align-items: flex-start; }
+ .sig-image-area { width: 100%; }
+ .submit-btn { width: 100%; justify-content: center; }
+}