Workshop photography quote

HTML for this example

Select the code and use your copy shortcut. Changes in this preview are temporary.

<!doctype html><html lang="en-US"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Workshop photography quote</title><style>*{box-sizing:border-box}html{color-scheme:light}body{margin:0;background:var(--bg,#f3f4f5);color:var(--ink,#192838);font:16px/1.5 system-ui,-apple-system,Segoe UI,sans-serif}main{max-width:1040px;margin:auto;padding:28px 28px 48px}header{margin-bottom:20px}h1{font-size:clamp(25px,4vw,36px);line-height:1.15;letter-spacing:-.035em;margin:6px 0 9px}h2{font-size:21px;line-height:1.25;margin:0 0 16px}h3{font-size:17px;margin:0 0 10px}p{margin:8px 0 16px}.eyebrow{font-size:12px;font-weight:750;text-transform:uppercase;letter-spacing:.12em;color:var(--accent,#275e86)}.subtle,small{color:#536170}.panel{padding:24px;background:white;border:1px solid var(--line,#d5dce2);border-radius:12px;margin:18px 0}.result{background:var(--result,#132f47);color:white;padding:26px;border-radius:14px;margin:18px 0}.result .subtle,.result small{color:#d7e3ed}.big{font-size:clamp(32px,6vw,56px);font-weight:750;line-height:1.15;letter-spacing:-.035em;margin:8px 0 14px;overflow-wrap:anywhere}.mono,pre,code{font-family:ui-monospace,Consolas,monospace;font-variant-numeric:tabular-nums}.mono{overflow-wrap:anywhere}pre{white-space:pre-wrap;overflow-wrap:anywhere;font-size:14px;line-height:1.7;margin:0}.grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:20px}.triple{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px}.fields{display:flex;gap:16px;flex-wrap:wrap;align-items:end}.fields>label{flex:1;min-width:130px}label{display:block;font-size:14px;font-weight:650}input,select,textarea{display:block;width:100%;font:inherit;font-size:16px;color:inherit;background:white;border:1px solid #a9b4bf;border-radius:6px;padding:11px 12px;margin-top:6px;min-width:0}input[type=checkbox]{display:inline;width:auto;margin:0 9px 0 0;accent-color:var(--accent,#275e86)}textarea{min-height:100px;resize:vertical}button{font:inherit;font-weight:650;padding:10px 17px;border:1px solid var(--accent,#275e86);background:var(--accent,#275e86);color:white;border-radius:6px;cursor:pointer;min-height:44px}button.secondary{background:white;color:var(--accent,#275e86)}button:disabled{opacity:.5;cursor:default}button:hover{filter:brightness(.94)}:focus-visible{outline:3px solid #e9a733;outline-offset:3px}.actions{display:flex;gap:10px;flex-wrap:wrap;margin-top:18px}.error{color:#a32325;font-weight:650;min-height:0}.error:not(:empty){padding:12px;border-left:4px solid #ae3237;background:#fff2f0;margin:14px 0}[hidden]{display:none!important}fieldset{border:1px solid #ccd4da;border-radius:8px;padding:18px;margin:0;min-width:0}legend{padding:0 7px;font-weight:750}table{width:100%;border-collapse:collapse;font-size:14px;table-layout:fixed}th{text-align:left;font-size:12px;text-transform:uppercase;letter-spacing:.04em;color:#536170}td,th{padding:12px 9px;border-bottom:1px solid #d9dfe4;vertical-align:top;overflow-wrap:anywhere}th:first-child,td:first-child{padding-left:0}dl{margin:0}dl>div{padding:10px 0;border-bottom:1px solid #d9dfe4}dt{font-size:13px;color:#536170}dd{margin:4px 0 0;font-weight:650}details{margin:20px 0;padding:18px 0;border-top:1px solid #cbd2d8}summary{cursor:pointer;font-weight:700}details p{margin-top:16px}a{color:var(--accent,#275e86)}.tag{display:inline-block;font-size:12px;font-weight:750;letter-spacing:.03em;border:1px solid currentColor;border-radius:5px;padding:4px 8px}.rule{border-top:1px solid #d9dfe4;padding-top:20px}.line{height:30px;border-bottom:1px solid #aab4be}.split-stat{display:flex;justify-content:space-between;gap:20px;align-items:baseline;flex-wrap:wrap}.result h2{color:white}.result table td,.result table th{border-color:#ffffff35;color:white}.result .eyebrow{color:#d7e3ed}.readout{padding:16px;background:#f0f4f6;border-radius:6px;margin-top:14px}.empty{padding:22px;border:1px dashed #a6b0ba;border-radius:8px;color:#536170;margin:18px 0}footer{font-size:13px;color:#536170;margin-top:24px}@media(max-width:720px){main{padding:20px}.panel,.result{padding:20px}.grid{gap:14px}.triple{gap:10px}}@media(max-width:540px){main{padding:16px}.grid{grid-template-columns:1fr}.panel,.result{padding:18px;border-radius:9px}.fields{gap:12px}.fields>label{min-width:100px}h1{font-size:27px}th,td{padding:10px 5px;font-size:13px}.triple{grid-template-columns:1fr}.big{font-size:38px}}.quote{background:#fff;padding:27px;border:1px solid #d8cfd0;border-top:5px solid #8a5363}.quote-top{display:flex;justify-content:space-between;align-items:start;gap:15px}.quote-top h2{margin:8px 0}.quote-top .tag{font-size:10px;max-width:130px}.quote-columns{display:grid;grid-template-columns:1fr 1fr;margin-top:20px}.quote-columns>div{padding:20px}.quote-columns>div:first-child{padding-left:0}.variation{background:#f5edf0;border-radius:8px}.quote-total{font:700 clamp(30px,5vw,46px)/1.2 Georgia,serif;margin:10px 0;overflow-wrap:anywhere}.quote-delta{border-top:1px solid #d8cfd0;padding-top:16px;margin-top:20px}@media(max-width:540px){.quote{padding:18px}.quote-top{display:block}.quote-top .tag{max-width:none}.quote-columns{grid-template-columns:1fr}.quote-columns>div:first-child{padding:0 0 20px}}</style></head><body style="--bg:#f4f0f1;--accent:#805063;--ink:#392c32;--line:#d8cfd0"><main><header><div class="eyebrow">Working example</div><h1>Workshop photography quote</h1><p class="subtle">Compare coverage changes while keeping image deliverables independently stated.</p></header><article class="quote"><div class="quote-top"><div><div class="eyebrow">Fictional community workshop</div><h2>October 20, 2026 · UTC</h2><p>One photographer · One workshop venue</p></div><span class="tag">PROPOSED · NOT BOOKED</span></div><div id="quoteResult" aria-live="polite"></div></article><section class="panel"><h2>Review a requested change</h2><div class="fields"><label for="end">Requested coverage end · UTC<select id="end"><option value="16">16:00 · original two hours</option><option value="17" selected>17:00 · requested three hours</option></select></label><label for="images">Requested edited JPEG count<input id="images" value="30" inputmode="decimal" autocomplete="off"></label></div><div class="actions"><button type="button" id="restore" class="secondary">Restore extension example</button></div></section><section class="panel"><h2>Shot priorities & delivery brief</h2><div class="grid"><div><h3>Requested moments</h3><ol><li>Group welcome</li><li>Materials table</li><li>Group activity</li></ol><p class="subtle">Confirm the actual sequence and venue access.</p></div><div><h3>October 27, 2026</h3><p>Proposed delivery date. Reconfirm capacity before promising it.</p><p id="imageBrief"></p></div></div></section><section class="panel"><h2>Unresolved before sending</h2><dl><div><dt>Actual venue/contact and access</dt><dd>TO CONFIRM</dd></div><div><dt>Permitted image uses / required permissions or consents</dt><dd>TO CONFIRM</dd></div><div><dt>Delivery arrangements / authorized booking policies</dt><dd>TO CONFIRM</dd></div><div><dt>Applicable fees and tax treatment</dt><dd>TO CONFIRM · excluded from known subtotal</dd></div></dl></section><details><summary>Quote calculation and boundaries</summary><p>Illustrative coverage is $90/hour. The unchanged 30-image editing/delivery proposal has a $60 fixed line. Original: 2 × $90 + $60 = $240. Extension: 3 × $90 + $60 = $330; difference $90. Changing the image count opens a separate scope and price review.</p><p>Rates are fictional, not market recommendations. No photographs, booking, payment, permissions, or sending are performed. Keep the authorized quote in the required business document format.</p></details><footer>Local review copy · No data is sent or saved.</footer></main><script>const $=id=>document.getElementById(id);const esc=s=>String(s).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));function round(n,d){return (2n*n+d)/(2n*d)}function dec(n,d,p=6){let x=round(n*10n**BigInt(p),d).toString().padStart(p+1,'0');return p?x.slice(0,-p)+'.'+x.slice(-p):x}function exact(n,d){let a=n/d,r=n%d,s='';for(let i=0;r&&i<36;i++){r*=10n;s+=r/d;r%=d}return a+(s?'.'+s:'')+(r?'…':'')}function num(s,name,max=1000000000,places=9,zero=false){if(!new RegExp('^[0-9]+(?:[.][0-9]{1,'+places+'})?$').test(s)||s.length>35)throw Error(name+': use a '+(zero?'nonnegative':'positive')+' dot-decimal number.');let [a,b='']=s.split('.'),d=10n**BigInt(b.length),n=BigInt(a+b);if((zero?n<0n:n<=0n)||n>BigInt(max)*d)throw Error(name+': value outside the supported range.');return {n,d,v:Number(n)/Number(d)}}function clear(){ $('result').hidden=true;$('trace').textContent='';$('empty').hidden=false;$('error').textContent='';}function reveal(){ $('result').hidden=false;$('empty').hidden=true;$('error').textContent='';}function error(e){clear();$('error').textContent=e.message;}function bindForm(run){$('form').addEventListener('submit',e=>{e.preventDefault();run()});$('form').addEventListener('input',clear);$('form').addEventListener('change',clear);}

function quote(end,count){if(!['16','17'].includes(end)||!/^[0-9]+$/.test(count)||Number(count)<1||Number(count)>10000)throw Error('Use the supplied end-time options and a whole image count from 1 to 10000.');let hours=Number(end)-14,coverage=hours*90,changed=Number(count)!==30;return {hours,coverage,total:changed?null:coverage+60,delta:changed?null:coverage+60-240,images:Number(count),changed}}
function render(){try{const r=quote($('end').value,$('images').value);$('quoteResult').innerHTML='<div class="quote-columns"><div><div class="eyebrow">Original proposal</div><div class="quote-total">$240</div><p>14:00–16:00 · 2 hours</p><p>Coverage 2 × $90 = $180<br>Editing/delivery $60</p><strong>30 edited JPEGs</strong></div><div class="variation"><div class="eyebrow">Requested variation</div><div class="quote-total">'+(r.total===null?'Requote needed':'$'+r.total)+'</div><p>14:00–'+$('end').value+':00 · '+r.hours+' hours</p><p>Coverage '+r.hours+' × $90 = $'+r.coverage+'<br>Editing/delivery '+(r.changed?'TO REQUOTE':'$60, scope unchanged')+'</p><strong>'+r.images+' edited JPEGs'+(r.changed?' requested':' · unchanged')+'</strong></div></div><p class="quote-delta">'+(r.changed?'Image scope changed. Revised editing fee and full service subtotal are unresolved.':(r.delta?'Known service subtotal increases by $'+r.delta+'.':'No coverage price change.')+' Extra hours do not add images automatically.')+'</p>';$('imageBrief').textContent=r.changed?r.images+' JPEGs requested; editing scope, price, and delivery capacity require review.':'30 edited JPEGs remain in the proposal; the extra hour does not make 45.';}catch(e){$('quoteResult').innerHTML='<p class="error" role="alert">'+esc(e.message)+'</p>';$('imageBrief').textContent='Correct the requested scope to review deliverables.'}}
['end','images'].forEach(x=>$(x).addEventListener('input',render));$('end').addEventListener('change',render);$('restore').onclick=()=>{$('end').value='17';$('images').value='30';render()};render();</script></body></html>