From 474b4b4fbf78a9b0ba0c34bc15b02d0984d5b202 Mon Sep 17 00:00:00 2001 From: Ron Lanzilotta Date: Wed, 7 Dec 2022 13:46:53 -0500 Subject: [PATCH 01/15] Used API key, finished --- lib/index.html | 1 - lib/script.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/lib/index.html b/lib/index.html index 0e97ce8..d32f536 100644 --- a/lib/index.html +++ b/lib/index.html @@ -31,7 +31,6 @@

Random Cat API

  • 14: Sinks
  • 15: Clothes
  • -
    diff --git a/lib/script.js b/lib/script.js index e69de29..306f30e 100644 --- a/lib/script.js +++ b/lib/script.js @@ -0,0 +1,28 @@ +const url = `https://api.thecatapi.com/v1/images/search`; + +let header = { + 'x-api-key':'live_MWhL6d9uLe1SLmWjK7AELzKHa6cXS2aR1rX0GWNmWQFAai1IQo1eumVnhYG7RdHf' +} + +const catButton = document.querySelector(`.randomButton`); + +catButton.addEventListener('click', handleClick) +// const container = document. + +function handleClick() { +fetch(url, { header }) + .then((res) => res.json()) + .then((res) => { + let randImage = document.querySelector(".randomCatImage"); + randImage.src = res[0].url; + }); +} + + + +// function displayData(data){} + +// function displayCat(url, ) { + + +// } \ No newline at end of file From 64aa4ff58543f6f241a88fc4a0c53a4051a96ad2 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Mar 2026 00:15:03 +0000 Subject: [PATCH 02/15] Add letter of support form for Joseph Fallert Brewery Replaces the Random Cat API exercise with a single-page form that lets users fill in their name, draw or upload a signature, and generate a filled PDF of the LPC support letter. Clicking the submit button downloads the PDF and opens a pre-addressed email draft to RFE@lpc.nyc.gov. https://claude.ai/code/session_01BmEJdM5rf1JB5BZEACUCFn --- lib/index.html | 174 ++++++++++++++---- lib/script.js | 343 +++++++++++++++++++++++++++++++++-- lib/style.css | 479 +++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 911 insertions(+), 85 deletions(-) diff --git a/lib/index.html b/lib/index.html index d32f536..0f73ea8 100644 --- a/lib/index.html +++ b/lib/index.html @@ -1,38 +1,138 @@ - - - - - Random Cat API - - - - - - -
    -

    Random Cat API

    -
    -
    - -
    -
    - - -
    -
    - -
      -
    • 1: Hats
    • -
    • 2: Space
    • -
    • 4: Sunglasses
    • -
    • 5: Boxes
    • -
    • 7: Ties
    • -
    • 14: Sinks
    • -
    • 15: Clothes
    • -
    -
    -
    - - - \ No newline at end of file + + + + + Letter of Support — Joseph Fallert Brewery + + + + + + + + + +
    + + +
    +
    Step 1
    +

    Your Full Name or Organization

    +

    This will replace "(Organization name)" throughout the letter.

    + +
    + + +
    +
    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 +
    +
    +
    + +
    +
    +
    +
    +
    + + +
    +
    Step 2
    +

    Your Signature

    +

    Draw with your finger or mouse, or upload an image of your signature.

    + +
    + + +
    + +
    +
    + +
    + +
    + + +
    + + +
    + + +

    + 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 306f30e..5b5c238 100644 --- a/lib/script.js +++ b/lib/script.js @@ -1,28 +1,337 @@ -const url = `https://api.thecatapi.com/v1/images/search`; +/* ── State ─────────────────────────────────────────────── */ +let userName = ''; +let signatureDataURL = ''; +let signatureMode = 'draw'; // 'draw' | 'upload' -let header = { - 'x-api-key':'live_MWhL6d9uLe1SLmWjK7AELzKHa6cXS2aR1rX0GWNmWQFAai1IQo1eumVnhYG7RdHf' +/* ── 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(); } -const catButton = document.querySelector(`.randomButton`); +/* ── Canvas drawing ────────────────────────────────────── */ +let drawing = false; +let lastX = 0, lastY = 0; -catButton.addEventListener('click', handleClick) -// const container = document. +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 handleClick() { -fetch(url, { header }) - .then((res) => res.json()) - .then((res) => { - let randImage = document.querySelector(".randomCatImage"); - randImage.src = res[0].url; - }); +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); +}); -// function displayData(data){} +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; +} -// function displayCat(url, ) { - +/* ── PDF generation ────────────────────────────────────── */ +submitBtn.addEventListener('click', generateAndSend); -// } \ No newline at end of file +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'); + + // Show notice + actionNotice.textContent = '✓ PDF downloaded — please attach it to the email draft that opens.'; + actionNotice.classList.remove('hidden'); + + // Open mailto + 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}` + ); + setTimeout(() => { + window.location.href = `mailto:RFE@lpc.nyc.gov?subject=${subject}&body=${body}`; + }, 400); +} 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; } +} From 75dc815f655b94ae0bf7b4ff41aa3530ad470710 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Mar 2026 00:22:13 +0000 Subject: [PATCH 03/15] Add GitHub Actions workflow to deploy lib/ to GitHub Pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deploys on every push to master. Once Pages is enabled in repo Settings → Pages (source: GitHub Actions), the site will be live at https://ronlanzilotta.github.io/random-cat-api/ https://claude.ai/code/session_01BmEJdM5rf1JB5BZEACUCFn --- .github/workflows/deploy-pages.yml | 37 ++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/deploy-pages.yml diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml new file mode 100644 index 0000000..d47901f --- /dev/null +++ b/.github/workflows/deploy-pages.yml @@ -0,0 +1,37 @@ +name: Deploy to GitHub Pages + +on: + push: + branches: [master] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: true + +jobs: + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Pages + uses: actions/configure-pages@v5 + + - name: Upload site artifact + uses: actions/upload-pages-artifact@v3 + with: + path: lib/ + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 From eca236f2e40fa0c2daf3c546dbd375503a797f8d Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Mar 2026 01:08:13 +0000 Subject: [PATCH 04/15] Add letter of support form for Joseph Fallert Brewery Single-page site: users enter their name, draw or upload a signature, preview the filled letter, then generate a PDF and open an email draft to RFE@lpc.nyc.gov with subject 'Letter of Support for Joseph Fallert Brewery'. Includes GitHub Actions workflow for automatic GitHub Pages deployment. https://claude.ai/code/session_01BmEJdM5rf1JB5BZEACUCFn --- .github/workflows/deploy-pages.yml | 4 +- index.html | 138 +++++++++ script.js | 337 +++++++++++++++++++++ style.css | 463 +++++++++++++++++++++++++++++ 4 files changed, 940 insertions(+), 2 deletions(-) create mode 100644 index.html create mode 100644 script.js create mode 100644 style.css diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml index d47901f..2320fd2 100644 --- a/.github/workflows/deploy-pages.yml +++ b/.github/workflows/deploy-pages.yml @@ -2,7 +2,7 @@ name: Deploy to GitHub Pages on: push: - branches: [master] + branches: [master, main] workflow_dispatch: permissions: @@ -30,7 +30,7 @@ jobs: - name: Upload site artifact uses: actions/upload-pages-artifact@v3 with: - path: lib/ + path: ./ - name: Deploy to GitHub Pages id: deployment diff --git a/index.html b/index.html new file mode 100644 index 0000000..0f73ea8 --- /dev/null +++ b/index.html @@ -0,0 +1,138 @@ + + + + + + Letter of Support — Joseph Fallert Brewery + + + + + + + + + +
    + + +
    +
    Step 1
    +

    Your Full Name or Organization

    +

    This will replace "(Organization name)" throughout the letter.

    + +
    + + +
    +
    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 +
    +
    +
    + +
    +
    +
    +
    +
    + + +
    +
    Step 2
    +

    Your Signature

    +

    Draw with your finger or mouse, or upload an image of your signature.

    + +
    + + +
    + +
    +
    + +
    + +
    + + +
    + + +
    + + +

    + 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/script.js b/script.js new file mode 100644 index 0000000..5b5c238 --- /dev/null +++ b/script.js @@ -0,0 +1,337 @@ +/* ── 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'); + + // Show notice + actionNotice.textContent = '✓ PDF downloaded — please attach it to the email draft that opens.'; + actionNotice.classList.remove('hidden'); + + // Open mailto + 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}` + ); + setTimeout(() => { + window.location.href = `mailto:RFE@lpc.nyc.gov?subject=${subject}&body=${body}`; + }, 400); +} diff --git a/style.css b/style.css new file mode 100644 index 0000000..9e26362 --- /dev/null +++ b/style.css @@ -0,0 +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 { + 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; +} + +/* ── 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; } + +.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; } +} From 60c2337139a7ad84f9233feeaf8fe5f9cbcef1ea Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 14 Mar 2026 20:42:03 +0000 Subject: [PATCH 05/15] Fix mailto draft not opening by using anchor click instead of window.location.href --- lib/script.js | 6 +++++- script.js | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/script.js b/lib/script.js index 5b5c238..f8f27b0 100644 --- a/lib/script.js +++ b/lib/script.js @@ -332,6 +332,10 @@ function generateAndSend() { `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}` ); setTimeout(() => { - window.location.href = `mailto:RFE@lpc.nyc.gov?subject=${subject}&body=${body}`; + const a = document.createElement('a'); + a.href = `mailto:RFE@lpc.nyc.gov?subject=${subject}&body=${body}`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); }, 400); } diff --git a/script.js b/script.js index 5b5c238..f8f27b0 100644 --- a/script.js +++ b/script.js @@ -332,6 +332,10 @@ function generateAndSend() { `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}` ); setTimeout(() => { - window.location.href = `mailto:RFE@lpc.nyc.gov?subject=${subject}&body=${body}`; + const a = document.createElement('a'); + a.href = `mailto:RFE@lpc.nyc.gov?subject=${subject}&body=${body}`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); }, 400); } From 2d5e8ce8e33bee05bfd492c8faa4ae52ce449322 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 14 Mar 2026 20:44:25 +0000 Subject: [PATCH 06/15] Show clickable email draft link in notice as reliable fallback for mailto auto-open --- lib/script.js | 19 +++++++++---------- script.js | 19 +++++++++---------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/lib/script.js b/lib/script.js index f8f27b0..860881c 100644 --- a/lib/script.js +++ b/lib/script.js @@ -322,20 +322,19 @@ function generateAndSend() { // Save doc.save('letter-of-support-fallert-brewery.pdf'); - // Show notice - actionNotice.textContent = '✓ PDF downloaded — please attach it to the email draft that opens.'; - actionNotice.classList.remove('hidden'); - - // Open mailto + // 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 fallback link + actionNotice.innerHTML = `✓ PDF downloaded — click here to open your email draft and attach the PDF.`; + actionNotice.classList.remove('hidden'); + + // Also attempt to auto-open setTimeout(() => { - const a = document.createElement('a'); - a.href = `mailto:RFE@lpc.nyc.gov?subject=${subject}&body=${body}`; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); + window.open(mailtoHref, '_self'); }, 400); } diff --git a/script.js b/script.js index f8f27b0..860881c 100644 --- a/script.js +++ b/script.js @@ -322,20 +322,19 @@ function generateAndSend() { // Save doc.save('letter-of-support-fallert-brewery.pdf'); - // Show notice - actionNotice.textContent = '✓ PDF downloaded — please attach it to the email draft that opens.'; - actionNotice.classList.remove('hidden'); - - // Open mailto + // 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 fallback link + actionNotice.innerHTML = `✓ PDF downloaded — click here to open your email draft and attach the PDF.`; + actionNotice.classList.remove('hidden'); + + // Also attempt to auto-open setTimeout(() => { - const a = document.createElement('a'); - a.href = `mailto:RFE@lpc.nyc.gov?subject=${subject}&body=${body}`; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); + window.open(mailtoHref, '_self'); }, 400); } From 2bebafc6eee1528276a9c74e97715e40a59a619d Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 14 Mar 2026 20:48:35 +0000 Subject: [PATCH 07/15] =?UTF-8?q?Replace=20mailto=20auto-open=20with=20exp?= =?UTF-8?q?licit=20green=20button=20=E2=80=94=20works=20reliably=20in=20Ch?= =?UTF-8?q?rome=20and=20all=20browsers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/script.js | 9 ++------- script.js | 9 ++------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/lib/script.js b/lib/script.js index 860881c..cf0a242 100644 --- a/lib/script.js +++ b/lib/script.js @@ -329,12 +329,7 @@ function generateAndSend() { ); const mailtoHref = `mailto:RFE@lpc.nyc.gov?subject=${subject}&body=${body}`; - // Show notice with fallback link - actionNotice.innerHTML = `✓ PDF downloaded — click here to open your email draft and attach the PDF.`; + // Show notice with explicit mailto button + actionNotice.innerHTML = `✓ PDF downloaded to your device. Open Email DraftAttach the PDF, then send.`; actionNotice.classList.remove('hidden'); - - // Also attempt to auto-open - setTimeout(() => { - window.open(mailtoHref, '_self'); - }, 400); } diff --git a/script.js b/script.js index 860881c..cf0a242 100644 --- a/script.js +++ b/script.js @@ -329,12 +329,7 @@ function generateAndSend() { ); const mailtoHref = `mailto:RFE@lpc.nyc.gov?subject=${subject}&body=${body}`; - // Show notice with fallback link - actionNotice.innerHTML = `✓ PDF downloaded — click here to open your email draft and attach the PDF.`; + // Show notice with explicit mailto button + actionNotice.innerHTML = `✓ PDF downloaded to your device. Open Email DraftAttach the PDF, then send.`; actionNotice.classList.remove('hidden'); - - // Also attempt to auto-open - setTimeout(() => { - window.open(mailtoHref, '_self'); - }, 400); } From a39c4b170ecd75352ee33c4eea64b5150a9d94e8 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 14 Mar 2026 21:11:46 +0000 Subject: [PATCH 08/15] Replace injected email link with proper anchor button matching blue submit style --- index.html | 4 ++++ script.js | 7 +++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index 0f73ea8..2d40afd 100644 --- a/index.html +++ b/index.html @@ -121,6 +121,10 @@

    Your Signature

    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/script.js b/script.js index cf0a242..ed5f1dd 100644 --- a/script.js +++ b/script.js @@ -42,6 +42,7 @@ const removeUploadBtn = document.getElementById('remove-upload-btn'); const submitBtn = document.getElementById('submit-btn'); const actionNotice = document.getElementById('action-notice'); +const emailBtn = document.getElementById('email-btn'); /* ── Init ──────────────────────────────────────────────── */ letterDate.textContent = `Date: ${todayStr}`; @@ -329,7 +330,9 @@ function generateAndSend() { ); 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 DraftAttach the PDF, then send.`; + // Show notice and email button + actionNotice.textContent = '✓ PDF downloaded to your device. Attach it to the email draft, then send.'; actionNotice.classList.remove('hidden'); + emailBtn.href = mailtoHref; + emailBtn.classList.remove('hidden'); } From 0f8b1c4f99f9f1699f12a129f340426fc02c6688 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 14 Mar 2026 21:16:45 +0000 Subject: [PATCH 09/15] Two distinct buttons: blue Generate PDF, green Open Email Draft --- index.html | 6 +++--- style.css | 30 ++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/index.html b/index.html index 2d40afd..602e550 100644 --- a/index.html +++ b/index.html @@ -117,11 +117,11 @@

    Your Signature

    - diff --git a/style.css b/style.css index 9e26362..44f4541 100644 --- a/style.css +++ b/style.css @@ -418,6 +418,36 @@ body { box-shadow: none; } +.email-btn { + display: inline-flex; + align-items: center; + gap: 10px; + font-family: var(--font-sans); + font-size: 1rem; + font-weight: 600; + color: #fff; + background: #15803d; + border: none; + border-radius: 8px; + padding: 14px 28px; + cursor: pointer; + text-decoration: none; + transition: background .15s, transform .1s, box-shadow .15s; + box-shadow: 0 2px 8px rgba(21,128,61,.25); +} + +.email-btn:hover { + background: #166534; + transform: translateY(-1px); + box-shadow: 0 4px 14px rgba(21,128,61,.35); +} + +.email-btn:active { + transform: translateY(0); +} + +.email-btn.hidden { display: none; } + .action-notice { font-size: .875rem; font-weight: 500; From c2452aa55ed63a096d45e61876274c4773535d78 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 14 Mar 2026 21:21:30 +0000 Subject: [PATCH 10/15] Fix email button: use button + window.location.href instead of anchor href --- index.html | 4 ++-- script.js | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/index.html b/index.html index 602e550..1b0c923 100644 --- a/index.html +++ b/index.html @@ -121,10 +121,10 @@

    Your Signature

    Generate PDF -

    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/script.js b/script.js index ed5f1dd..8f08ae6 100644 --- a/script.js +++ b/script.js @@ -2,6 +2,7 @@ let userName = ''; let signatureDataURL = ''; let signatureMode = 'draw'; // 'draw' | 'upload' +let currentMailtoHref = ''; /* ── Date helpers ──────────────────────────────────────── */ const MONTHS = [ @@ -219,6 +220,7 @@ function checkReady() { /* ── PDF generation ────────────────────────────────────── */ submitBtn.addEventListener('click', generateAndSend); +emailBtn.addEventListener('click', () => { window.location.href = currentMailtoHref; }); function generateAndSend() { const { jsPDF } = window.jspdf; @@ -331,8 +333,8 @@ function generateAndSend() { const mailtoHref = `mailto:RFE@lpc.nyc.gov?subject=${subject}&body=${body}`; // Show notice and email button + currentMailtoHref = mailtoHref; actionNotice.textContent = '✓ PDF downloaded to your device. Attach it to the email draft, then send.'; actionNotice.classList.remove('hidden'); - emailBtn.href = mailtoHref; emailBtn.classList.remove('hidden'); } From d209063d0eb3f01fffc7445610d499c211e5f2f2 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 14 Mar 2026 21:29:54 +0000 Subject: [PATCH 11/15] Restore email button as anchor tag with href set directly The previous commit switched from to -

    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/script.js b/script.js index 8f08ae6..91758c1 100644 --- a/script.js +++ b/script.js @@ -2,7 +2,6 @@ let userName = ''; let signatureDataURL = ''; let signatureMode = 'draw'; // 'draw' | 'upload' -let currentMailtoHref = ''; /* ── Date helpers ──────────────────────────────────────── */ const MONTHS = [ @@ -220,7 +219,6 @@ function checkReady() { /* ── PDF generation ────────────────────────────────────── */ submitBtn.addEventListener('click', generateAndSend); -emailBtn.addEventListener('click', () => { window.location.href = currentMailtoHref; }); function generateAndSend() { const { jsPDF } = window.jspdf; @@ -333,7 +331,7 @@ function generateAndSend() { const mailtoHref = `mailto:RFE@lpc.nyc.gov?subject=${subject}&body=${body}`; // Show notice and email button - currentMailtoHref = mailtoHref; + emailBtn.href = mailtoHref; actionNotice.textContent = '✓ PDF downloaded to your device. Attach it to the email draft, then send.'; actionNotice.classList.remove('hidden'); emailBtn.classList.remove('hidden'); From cc81e81da60b571c093be3632ef40e45dcec2d56 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 14 Mar 2026 21:35:40 +0000 Subject: [PATCH 12/15] Inject email anchor with mailto href directly into DOM at click time Rather than pre-rendering a hidden button and setting its href dynamically, create the element with the full mailto URL baked into the href at the moment the PDF is generated. This guarantees the link is present and correct. https://claude.ai/code/session_01BmEJdM5rf1JB5BZEACUCFn --- index.html | 5 +---- script.js | 7 +++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/index.html b/index.html index a6e79a9..4205076 100644 --- a/index.html +++ b/index.html @@ -121,10 +121,7 @@

    Your Signature

    Generate PDF -
    +

    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/script.js b/script.js index 91758c1..b91585e 100644 --- a/script.js +++ b/script.js @@ -42,7 +42,7 @@ const removeUploadBtn = document.getElementById('remove-upload-btn'); const submitBtn = document.getElementById('submit-btn'); const actionNotice = document.getElementById('action-notice'); -const emailBtn = document.getElementById('email-btn'); +const emailBtnWrap = document.getElementById('email-btn-wrap'); /* ── Init ──────────────────────────────────────────────── */ letterDate.textContent = `Date: ${todayStr}`; @@ -330,9 +330,8 @@ function generateAndSend() { ); const mailtoHref = `mailto:RFE@lpc.nyc.gov?subject=${subject}&body=${body}`; - // Show notice and email button - emailBtn.href = mailtoHref; + // Show notice and inject email button with live href actionNotice.textContent = '✓ PDF downloaded to your device. Attach it to the email draft, then send.'; actionNotice.classList.remove('hidden'); - emailBtn.classList.remove('hidden'); + emailBtnWrap.innerHTML = ``; } From 1fcfbd888865d8e91221a874c2859ebd37850f2d Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 14 Mar 2026 21:41:19 +0000 Subject: [PATCH 13/15] Remove Open Email Draft button and related code Simplified post-generation UX to just a confirmation notice telling users to email the PDF manually to RFE@lpc.nyc.gov. https://claude.ai/code/session_01BmEJdM5rf1JB5BZEACUCFn --- index.html | 4 ---- script.js | 13 ++----------- style.css | 37 ------------------------------------- 3 files changed, 2 insertions(+), 52 deletions(-) diff --git a/index.html b/index.html index 4205076..be854a6 100644 --- a/index.html +++ b/index.html @@ -121,10 +121,6 @@

    Your Signature

    Generate PDF -
    -

    - 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/script.js b/script.js index b91585e..f2399ff 100644 --- a/script.js +++ b/script.js @@ -42,7 +42,6 @@ const removeUploadBtn = document.getElementById('remove-upload-btn'); const submitBtn = document.getElementById('submit-btn'); const actionNotice = document.getElementById('action-notice'); -const emailBtnWrap = document.getElementById('email-btn-wrap'); /* ── Init ──────────────────────────────────────────────── */ letterDate.textContent = `Date: ${todayStr}`; @@ -323,15 +322,7 @@ function generateAndSend() { // 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 and inject email button with live href - actionNotice.textContent = '✓ PDF downloaded to your device. Attach it to the email draft, then send.'; + // Show success notice + actionNotice.textContent = '✓ PDF downloaded to your device. Please email it to RFE@lpc.nyc.gov.'; actionNotice.classList.remove('hidden'); - emailBtnWrap.innerHTML = ``; } diff --git a/style.css b/style.css index 44f4541..ea150aa 100644 --- a/style.css +++ b/style.css @@ -418,36 +418,6 @@ body { box-shadow: none; } -.email-btn { - display: inline-flex; - align-items: center; - gap: 10px; - font-family: var(--font-sans); - font-size: 1rem; - font-weight: 600; - color: #fff; - background: #15803d; - border: none; - border-radius: 8px; - padding: 14px 28px; - cursor: pointer; - text-decoration: none; - transition: background .15s, transform .1s, box-shadow .15s; - box-shadow: 0 2px 8px rgba(21,128,61,.25); -} - -.email-btn:hover { - background: #166534; - transform: translateY(-1px); - box-shadow: 0 4px 14px rgba(21,128,61,.35); -} - -.email-btn:active { - transform: translateY(0); -} - -.email-btn.hidden { display: none; } - .action-notice { font-size: .875rem; font-weight: 500; @@ -460,13 +430,6 @@ body { .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; From d05c43879a675964f84aa1ac6b35066d9b818a9d Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 14 Mar 2026 21:43:51 +0000 Subject: [PATCH 14/15] Remove header and footer; add canonical URL for GitHub Pages - Removed site header (Letter of Support title block) - Removed footer (Questions? contact line) - Added canonical link tag pointing to https://ronlanzilotta.github.io/joseph-fallert-letter - Cleaned up orphaned header/footer CSS https://claude.ai/code/session_01BmEJdM5rf1JB5BZEACUCFn --- index.html | 15 ++------------- style.css | 52 ---------------------------------------------------- 2 files changed, 2 insertions(+), 65 deletions(-) diff --git a/index.html b/index.html index be854a6..5c74912 100644 --- a/index.html +++ b/index.html @@ -8,17 +8,10 @@ + - -
    @@ -125,11 +118,7 @@

    Your Signature

    - - - + diff --git a/style.css b/style.css index ea150aa..2e4fed9 100644 --- a/style.css +++ b/style.css @@ -36,42 +36,6 @@ body { 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; @@ -430,22 +394,6 @@ body { .action-notice.hidden { display: none; } -/* ── 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; } From 8b3bb2a44389eef2c624b8322c362ff922e6a8b9 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 14 Mar 2026 21:56:05 +0000 Subject: [PATCH 15/15] Fix PDF page overflow: add automatic page break logic Content was silently clipped at page 1 boundary, cutting off paragraphs and the signature. Added checkPageBreak() before each paragraph and the closing/signature block to insert a new page when needed. https://claude.ai/code/session_01BmEJdM5rf1JB5BZEACUCFn --- script.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/script.js b/script.js index f2399ff..b6b67f7 100644 --- a/script.js +++ b/script.js @@ -260,9 +260,20 @@ function generateAndSend() { 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; } @@ -295,6 +306,7 @@ function generateAndSend() { y += lineH * 0.5; // Closing + checkPageBreak(lineH * 1.5 + 22 + 1 + lineH * 2); doc.text('Sincerely,', marginL, y); y += lineH * 1.5;