Created
October 5, 2026 22:31
-
-
Save userdehghani/0c08f5d10d5f69f3671db71d826d2bb3 to your computer and use it in GitHub Desktop.
Customer invoices and history — before / after
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!doctype html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |
| <title>Customer invoices and history — before / after</title> | |
| <style> | |
| :root { | |
| color-scheme: light; | |
| --bg: #f4f4f5; | |
| --ink: #18181b; | |
| --muted: #71717a; | |
| --line: #e4e4e7; | |
| --card: #ffffff; | |
| --fill: #f4f4f5; | |
| --soft: #fafafa; | |
| } | |
| * { box-sizing: border-box; } | |
| body { | |
| margin: 0; | |
| min-height: 100vh; | |
| font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; | |
| background: var(--bg); | |
| color: var(--ink); | |
| } | |
| header { | |
| max-width: 880px; | |
| margin: 0 auto; | |
| padding: 28px 20px 0; | |
| } | |
| h1 { margin: 0; font-size: 18px; font-weight: 650; letter-spacing: -0.02em; } | |
| p.lead { margin: 6px 0 0; color: var(--muted); font-size: 13px; max-width: 46rem; line-height: 1.45; } | |
| .controls { | |
| display: flex; | |
| flex-wrap: wrap; | |
| align-items: center; | |
| justify-content: space-between; | |
| gap: 12px; | |
| max-width: 880px; | |
| margin: 16px auto 0; | |
| padding: 0 20px; | |
| } | |
| .switch, .scenes { | |
| display: inline-flex; | |
| padding: 3px; | |
| border: 1px solid var(--line); | |
| border-radius: 999px; | |
| background: white; | |
| } | |
| .switch button, .scenes button { | |
| border: 0; | |
| background: transparent; | |
| border-radius: 999px; | |
| padding: 6px 12px; | |
| font: inherit; | |
| font-size: 13px; | |
| color: var(--muted); | |
| cursor: pointer; | |
| } | |
| .switch button[aria-pressed="true"], .scenes button[aria-pressed="true"] { | |
| background: var(--ink); | |
| color: white; | |
| } | |
| .stage { | |
| max-width: 880px; | |
| margin: 16px auto 48px; | |
| padding: 0 20px; | |
| } | |
| .panel { | |
| background: white; | |
| border: 1px solid var(--line); | |
| border-radius: 16px; | |
| overflow: hidden; | |
| } | |
| .panel h2 { | |
| margin: 0; | |
| padding: 16px 18px 0; | |
| font-size: 13px; | |
| font-weight: 650; | |
| } | |
| .panel .note { | |
| margin: 4px 18px 0; | |
| color: var(--muted); | |
| font-size: 12px; | |
| } | |
| .body { padding: 16px 18px 18px; } | |
| .title-row { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 10px; } | |
| .title-row strong { font-size: 15px; } | |
| .count { color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; } | |
| .count b { color: var(--ink); font-weight: 600; } | |
| .search { | |
| height: 36px; | |
| border: 1px solid var(--line); | |
| border-radius: 8px; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| padding: 0 12px; | |
| color: var(--muted); | |
| font-size: 13px; | |
| margin-bottom: 8px; | |
| } | |
| .row { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| min-height: 56px; | |
| padding: 8px; | |
| border-radius: 12px; | |
| } | |
| .row.selected { background: #f4f4f5; } | |
| .row.disabled { opacity: 0.45; } | |
| .box { | |
| width: 16px; height: 16px; border-radius: 6px; border: 1px solid #d4d4d8; flex: none; | |
| display: grid; place-items: center; font-size: 11px; color: white; | |
| } | |
| .box.on { background: #18181b; border-color: #18181b; } | |
| .icon { width: 28px; text-align: center; color: var(--muted); flex: none; } | |
| .name { min-width: 0; flex: 1; } | |
| .name span { display: block; font-size: 14px; } | |
| .name small { display: block; color: var(--muted); font-size: 12px; margin-top: 2px; } | |
| .price { font-variant-numeric: tabular-nums; font-size: 14px; } | |
| .single { | |
| height: 40px; | |
| border: 1px solid var(--line); | |
| border-radius: 8px; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| padding: 0 10px; | |
| font-size: 14px; | |
| } | |
| .single span { flex: 1; } | |
| .chev { color: var(--muted); } | |
| table { width: 100%; border-collapse: collapse; font-size: 13px; } | |
| th, td { text-align: left; padding: 8px 12px; border-bottom: 1px solid #e4e4e7; } | |
| th.loud { background: #f4f4f5; color: #18181b; font-size: 14px; font-weight: 600; padding: 10px 12px; } | |
| th.quiet { background: #fafafa; color: #71717a; font-size: 12px; font-weight: 500; padding: 8px 12px; border-bottom-color: #e4e4e7; } | |
| td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; } | |
| .paper { background: #fff; } | |
| .paper-top { display: flex; justify-content: space-between; padding: 18px 18px 8px; } | |
| .paper-top b { font-size: 16px; } | |
| .paper-top span { color: var(--muted); font-size: 12px; } | |
| .badge { | |
| display: inline-flex; | |
| align-items: center; | |
| height: 22px; | |
| padding: 0 8px; | |
| border-radius: 999px; | |
| background: #f4f4f5; | |
| color: #3f3f46; | |
| font-size: 12px; | |
| } | |
| .hist-old { display: grid; grid-template-columns: minmax(0,1fr) 5.6rem 28px; gap: 4px; align-items: center; padding: 12px 14px; border-bottom: 1px solid #ececee; } | |
| .hist-old .svc { display: flex; align-items: center; gap: 10px; min-width: 0; font-size: 14px; } | |
| .date-pill { | |
| height: 28px; | |
| border: 1px solid var(--line); | |
| border-radius: 6px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 4px; | |
| color: var(--muted); | |
| font-size: 12px; | |
| } | |
| .well { | |
| width: 36px; height: 36px; display: grid; place-items: center; color: var(--muted); flex: none; | |
| } | |
| .status { | |
| display: inline-flex; align-items: center; height: 22px; padding: 0 8px; border-radius: 999px; | |
| border: 1px solid #bbf7d0; background: #f0fdf4; color: #166534; font-size: 12px; | |
| } | |
| [hidden] { display: none !important; } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <h1>Customer invoices and history</h1> | |
| <p class="lead">An invoice can include several services, and each one is its own line on the PDF. History and payment history column titles match the file manager table.</p> | |
| </header> | |
| <div class="controls"> | |
| <div class="scenes" role="tablist"> | |
| <button type="button" data-scene="services" aria-pressed="true">Services</button> | |
| <button type="button" data-scene="invoice" aria-pressed="false">Invoice file</button> | |
| <button type="button" data-scene="history" aria-pressed="false">History</button> | |
| <button type="button" data-scene="payments" aria-pressed="false">Payments</button> | |
| </div> | |
| <div class="switch" role="group" aria-label="Before or after"> | |
| <button type="button" data-mode="before" aria-pressed="false">Before</button> | |
| <button type="button" data-mode="after" aria-pressed="true">After</button> | |
| </div> | |
| </div> | |
| <div class="stage"> | |
| <section class="panel" data-panel="services"> | |
| <h2>Create invoice · Services</h2> | |
| <p class="note" data-before>One service. Picking another replaces it.</p> | |
| <p class="note" data-after hidden>Several services stay in the list. A mismatched currency stays visible and disabled.</p> | |
| <div class="body" data-before> | |
| <div class="title-row"><strong>Service</strong></div> | |
| <div class="single"> | |
| <span class="icon">▢</span> | |
| <span>Listing<small style="display:block;color:#71717a;font-size:12px">Service · LST-1</small></span> | |
| <span class="price">12 000 ₽</span> | |
| <span class="chev">›</span> | |
| </div> | |
| </div> | |
| <div class="body" data-after hidden> | |
| <div class="title-row"> | |
| <strong>Services</strong> | |
| <span class="count">2 selected · <b>20 000 ₽</b></span> | |
| </div> | |
| <div class="search">⌕ Search services</div> | |
| <div class="row selected"><span class="box on">✓</span><span class="icon">▢</span><span class="name"><span>Listing</span><small>Service · LST-1</small></span><span class="price">12 000 ₽</span></div> | |
| <div class="row selected"><span class="box on">✓</span><span class="icon">▢</span><span class="name"><span>Photography</span><small>Service · PHT-2</small></span><span class="price">8 000 ₽</span></div> | |
| <div class="row disabled"><span class="box"></span><span class="icon">▢</span><span class="name"><span>Staging</span><small>Service · USD</small></span><span class="price">$200</span></div> | |
| </div> | |
| </section> | |
| <section class="panel" data-panel="invoice" hidden> | |
| <h2>Invoice PDF</h2> | |
| <p class="note" data-before>Both services were stored as one line.</p> | |
| <p class="note" data-after hidden>Each selected service has its own row.</p> | |
| <div class="paper"> | |
| <div class="paper-top"><b>Invoice</b><span>No. 1042</span></div> | |
| <table> | |
| <thead> | |
| <tr><th class="quiet">Description</th><th class="quiet num">Qty</th><th class="quiet num">Price</th><th class="quiet num">Amount</th></tr> | |
| </thead> | |
| <tbody data-before> | |
| <tr><td>Listing, Photography</td><td class="num">1</td><td class="num">20 000 ₽</td><td class="num">20 000 ₽</td></tr> | |
| </tbody> | |
| <tbody data-after hidden> | |
| <tr><td>Listing</td><td class="num">1</td><td class="num">12 000 ₽</td><td class="num">12 000 ₽</td></tr> | |
| <tr><td>Photography</td><td class="num">1</td><td class="num">8 000 ₽</td><td class="num">8 000 ₽</td></tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| </section> | |
| <section class="panel" data-panel="history" hidden> | |
| <h2>Customer history</h2> | |
| <p class="note" data-before>Rows with no column titles. The date is a bordered control.</p> | |
| <p class="note" data-after hidden>Name, Type, and Date use the file manager title style. The date is a badge.</p> | |
| <div data-before> | |
| <div class="hist-old"><div class="svc"><span>▢</span> Listing</div><div class="date-pill">📅 Oct 02</div><span>✎</span></div> | |
| <div class="hist-old"><div class="svc"><span>▢</span> Photography</div><div class="date-pill">📅 Sep 18</div><span>✎</span></div> | |
| </div> | |
| <table data-after hidden> | |
| <thead> | |
| <tr> | |
| <th class="quiet">Name</th> | |
| <th class="quiet">Type</th> | |
| <th class="quiet">Date</th> | |
| <th class="quiet"></th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <td><div class="svc" style="display:flex;gap:10px;align-items:center"><span class="well">▢</span><span>Listing<br><small style="color:#71717a">Signed</small></span></div></td> | |
| <td style="color:#71717a">Service</td> | |
| <td><span class="badge">Oct 02</span></td> | |
| <td style="text-align:right;color:#71717a">⋯</td> | |
| </tr> | |
| <tr> | |
| <td><div class="svc" style="display:flex;gap:10px;align-items:center"><span class="well">▢</span><span>Photography</span></div></td> | |
| <td style="color:#71717a">Service</td> | |
| <td><span class="badge">Sep 18</span></td> | |
| <td style="text-align:right;color:#71717a">⋯</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </section> | |
| <section class="panel" data-panel="payments" hidden> | |
| <h2>Payment history</h2> | |
| <p class="note" data-before>Column titles are bold, on a muted fill.</p> | |
| <p class="note" data-after hidden>Same titles as the file manager: small, muted, on the page background.</p> | |
| <table> | |
| <thead data-before> | |
| <tr> | |
| <th class="loud">Invoice</th> | |
| <th class="loud">Date</th> | |
| <th class="loud">Amount</th> | |
| <th class="loud">Payment method</th> | |
| <th class="loud">Status</th> | |
| <th class="loud" style="text-align:right">Actions</th> | |
| </tr> | |
| </thead> | |
| <thead data-after hidden> | |
| <tr> | |
| <th class="quiet">Invoice</th> | |
| <th class="quiet">Date</th> | |
| <th class="quiet">Amount</th> | |
| <th class="quiet">Payment method</th> | |
| <th class="quiet">Status</th> | |
| <th class="quiet" style="text-align:right">Actions</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <td>1042</td> | |
| <td>Oct 02</td> | |
| <td>20 000 ₽</td> | |
| <td>Card</td> | |
| <td><span class="status">Paid</span></td> | |
| <td style="text-align:right;color:#71717a">⋮</td> | |
| </tr> | |
| <tr> | |
| <td>1038</td> | |
| <td>Sep 18</td> | |
| <td>12 000 ₽</td> | |
| <td>Cash</td> | |
| <td><span class="status">Paid</span></td> | |
| <td style="text-align:right;color:#71717a">⋮</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </section> | |
| </div> | |
| <script> | |
| const scenes = document.querySelectorAll("[data-scene]"); | |
| const panels = document.querySelectorAll("[data-panel]"); | |
| const modes = document.querySelectorAll("[data-mode]"); | |
| let scene = "services"; | |
| let mode = "after"; | |
| function render() { | |
| panels.forEach(panel => { | |
| panel.hidden = panel.dataset.panel !== scene; | |
| }); | |
| document.querySelectorAll("[data-before], [data-after]").forEach(node => { | |
| const panel = node.closest("[data-panel]"); | |
| const show = panel && panel.dataset.panel === scene && node.hasAttribute("data-" + mode); | |
| node.hidden = !show; | |
| }); | |
| scenes.forEach(button => button.setAttribute("aria-pressed", String(button.dataset.scene === scene))); | |
| modes.forEach(button => button.setAttribute("aria-pressed", String(button.dataset.mode === mode))); | |
| } | |
| scenes.forEach(button => button.addEventListener("click", () => { scene = button.dataset.scene; render(); })); | |
| modes.forEach(button => button.addEventListener("click", () => { mode = button.dataset.mode; render(); })); | |
| render(); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment