{"id":"4c5d488b-4d72-47f5-9781-0f3e1bd94149","name":"Reddit Comment Thread","slug":"reddit-comment-thread","category":"post-screenshots","description":"A dark-mode Reddit comment thread as a shareable image: up to three comments at any indent, real vote arrows, threadlines, OP badges and a “more replies” pill. Quote what people actually said about you without pasting a screenshot.","sample_params":{"c1_author":"fishter_uk","c1_score":"5","c1_text":"Cons: It's not permanent. The provider can simply turn off your QR codes whenever they want, or when they stop paying for the domain name.\n\nPros: It's easy, if you can't work out how to create QR codes that point to URLs on your own website.","c1_time":"6mo ago","c2_author":"One-Exit-8826","c2_depth":"0","c2_score":"1","c2_text":"Its a scam. You can make your own QR codes. Why on earth would you want to pay a company to force you to pay?","c2_time":"5mo ago","c3_author":"vladuxs1","c3_depth":"1","c3_op":"yes","c3_score":"1","c3_text":"not everyone has knowledge or skills or time to make qr codes by them self. also there a lot of cases where you want dynamic qr code with redirects and stats, not simple static qr code you described","c3_time":"5mo ago","more_replies":"1 more reply","subreddit":"r/qrcode","title":"What paid dynamic QR code services do you use?"},"spec":{"name":"reddit-comment-thread","width":1200,"height":1000,"scale":2,"html":"\u003c!doctype html\u003e\n\u003chtml\u003e\n\u003chead\u003e\n\u003cmeta charset='utf-8'\u003e\n\u003cstyle\u003e\n*{margin:0;padding:0;box-sizing:border-box}\nhtml,body{width:100%;height:100%}\nbody{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;\n  overflow:hidden;background:var(--bg);color:var(--fg);display:flex;align-items:center;justify-content:center}\n:root{--bg:#0b1416;--fg:#eaeff1;--weak:#8ba2ad;--line:#39474d;--pill:#3d4b51;--op:#4fbcff}\nmain{width:100%;padding:0 56px;overflow:hidden}\nheader{margin-bottom:34px}\n.sub{font-size:22px;font-weight:700;color:var(--weak);letter-spacing:.01em}\nh1{margin-top:12px;font-size:38px;font-weight:700;letter-spacing:-0.015em;line-height:1.22;\n  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}\n.thread{zoom:1}\n/* One comment = Reddit's two-column grid: the rail (avatar, threadline,\n   collapse dot) and the content column (meta, body, votes). */\n.c{position:relative;display:grid;grid-template-columns:44px 1fr;column-gap:12px;padding-bottom:6px}\n.rail{grid-column:1;grid-row:1;display:flex;align-items:center;height:44px}\n.av{width:44px;height:44px;border-radius:50%;overflow:hidden;display:flex;align-items:center;\n  justify-content:center;font-size:20px;font-weight:700;color:#0b1416}\n.av img{width:100%;height:100%;object-fit:cover;display:block}\n/* The rail line runs down from the avatar and 26px past the comment, where\n   the child's L-connector picks it up: left:-34px lands its left border\n   exactly on the parent rail's centre one indent step back. */\n/* span 2, not \"2 / -1\": the rows here are implicit, and a negative end line\n   would resolve against the empty explicit grid and land back on row 1. */\n.tl{grid-column:1;grid-row:2 / span 2;width:2px;justify-self:center;background:var(--line);\n  margin-bottom:-26px;display:none}\n.conn{position:absolute;left:-34px;top:-26px;width:28px;height:48px;display:none;\n  border-left:2px solid var(--line);border-bottom:2px solid var(--line);border-bottom-left-radius:12px}\n.cl{grid-column:1;grid-row:3;justify-self:center;align-self:center;width:22px;height:22px;\n  background:var(--bg);color:var(--weak);display:none;z-index:1}\n.cl svg{width:22px;height:22px;fill:currentColor;display:block}\n.meta{grid-column:2;grid-row:1;display:flex;align-items:center;height:44px;font-size:21px;min-width:0}\n.author{font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:22em}\n.op{margin-left:8px;color:var(--op);font-weight:700;font-size:19px;letter-spacing:.04em}\n.dot{margin:0 8px;color:var(--weak)}\n.time{color:var(--weak);white-space:nowrap}\n.text{grid-column:2;grid-row:2;margin-top:2px;font-size:25px;line-height:1.45;white-space:pre-wrap;\n  overflow-wrap:break-word;display:-webkit-box;-webkit-line-clamp:8;-webkit-box-orient:vertical;overflow:hidden}\n.votes{grid-column:2;grid-row:3;display:flex;align-items:center;gap:9px;margin-top:14px;\n  color:var(--weak);font-size:20px;font-weight:600}\n.votes svg{width:23px;height:23px;fill:currentColor;display:block}\n.more{position:relative;display:inline-flex;align-items:center;gap:7px;border:1px solid var(--pill);\n  border-radius:999px;padding:9px 20px;font-size:20px;font-weight:600;color:#d3dee3;margin-top:4px}\n.more .conn{top:-26px;height:52px}\n.more svg{width:20px;height:20px;fill:currentColor;display:block}\n\u003c/style\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n\u003cmain\u003e\n  \u003cheader id='head'\u003e\n    \u003cdiv class='sub' id='sub'\u003e{{.subreddit}}\u003c/div\u003e\n    \u003ch1 id='title'\u003e{{.title}}\u003c/h1\u003e\n  \u003c/header\u003e\n  \u003cdiv class='thread' id='thread'\u003e\n    \u003carticle class='c' data-depth='0' data-op='{{.c1_op}}'\u003e\n      \u003cdiv class='rail'\u003e\u003cdiv class='av' data-name='{{.c1_author}}'\u003e{{if .c1_avatar_url}}\u003cimg src='{{.c1_avatar_url}}' alt=''\u003e{{end}}\u003c/div\u003e\u003c/div\u003e\n      \u003cdiv class='tl'\u003e\u003c/div\u003e\u003cspan class='cl'\u003e\u003c/span\u003e\n      \u003cdiv class='meta'\u003e\u003cspan class='author'\u003e{{.c1_author}}\u003c/span\u003e\u003cspan class='op'\u003e\u003c/span\u003e\u003cspan class='dot'\u003e\u0026bull;\u003c/span\u003e\u003cspan class='time'\u003e{{.c1_time}}\u003c/span\u003e\u003c/div\u003e\n      \u003cdiv class='text'\u003e{{.c1_text}}\u003c/div\u003e\n      \u003cdiv class='votes'\u003e\u003cspan class='up'\u003e\u003c/span\u003e\u003cspan\u003e{{.c1_score}}\u003c/span\u003e\u003cspan class='down'\u003e\u003c/span\u003e\u003c/div\u003e\n    \u003c/article\u003e\n    \u003carticle class='c' data-depth='{{.c2_depth}}' data-op='{{.c2_op}}' data-text='{{.c2_text}}'\u003e\n      \u003cdiv class='rail'\u003e\u003cdiv class='av' data-name='{{.c2_author}}'\u003e{{if .c2_avatar_url}}\u003cimg src='{{.c2_avatar_url}}' alt=''\u003e{{end}}\u003c/div\u003e\u003c/div\u003e\n      \u003cdiv class='tl'\u003e\u003c/div\u003e\u003cspan class='cl'\u003e\u003c/span\u003e\n      \u003cdiv class='meta'\u003e\u003cspan class='author'\u003e{{.c2_author}}\u003c/span\u003e\u003cspan class='op'\u003e\u003c/span\u003e\u003cspan class='dot'\u003e\u0026bull;\u003c/span\u003e\u003cspan class='time'\u003e{{.c2_time}}\u003c/span\u003e\u003c/div\u003e\n      \u003cdiv class='text'\u003e{{.c2_text}}\u003c/div\u003e\n      \u003cdiv class='votes'\u003e\u003cspan class='up'\u003e\u003c/span\u003e\u003cspan\u003e{{.c2_score}}\u003c/span\u003e\u003cspan class='down'\u003e\u003c/span\u003e\u003c/div\u003e\n    \u003c/article\u003e\n    \u003carticle class='c' data-depth='{{.c3_depth}}' data-op='{{.c3_op}}' data-text='{{.c3_text}}'\u003e\n      \u003cdiv class='rail'\u003e\u003cdiv class='av' data-name='{{.c3_author}}'\u003e{{if .c3_avatar_url}}\u003cimg src='{{.c3_avatar_url}}' alt=''\u003e{{end}}\u003c/div\u003e\u003c/div\u003e\n      \u003cdiv class='tl'\u003e\u003c/div\u003e\u003cspan class='cl'\u003e\u003c/span\u003e\n      \u003cdiv class='meta'\u003e\u003cspan class='author'\u003e{{.c3_author}}\u003c/span\u003e\u003cspan class='op'\u003e\u003c/span\u003e\u003cspan class='dot'\u003e\u0026bull;\u003c/span\u003e\u003cspan class='time'\u003e{{.c3_time}}\u003c/span\u003e\u003c/div\u003e\n      \u003cdiv class='text'\u003e{{.c3_text}}\u003c/div\u003e\n      \u003cdiv class='votes'\u003e\u003cspan class='up'\u003e\u003c/span\u003e\u003cspan\u003e{{.c3_score}}\u003c/span\u003e\u003cspan class='down'\u003e\u003c/span\u003e\u003c/div\u003e\n    \u003c/article\u003e\n    \u003cdiv class='more' id='more'\u003e\u003cspan\u003e{{.more_replies}}\u003c/span\u003e\u003cspan class='caret'\u003e\u003c/span\u003e\u003c/div\u003e\n  \u003c/div\u003e\n\u003c/main\u003e\n\u003cscript\u003e\n// Reddit's own icon set, so the vote arrows and the collapse/caret dots\n// read exactly like a real screenshot.\nvar ICON = {\n  up: 'M10 19a3.966 3.966 0 01-3.96-3.962V10.98H2.838a1.731 1.731 0 01-1.605-1.073 1.734 1.734 0 01.377-1.895L9.364.254a.925.925 0 011.272 0l7.754 7.759c.498.499.646 1.242.376 1.894-.27.652-.9 1.073-1.605 1.073h-3.202v4.058A3.965 3.965 0 019.999 19H10zM2.989 9.179H7.84v5.731c0 1.13.81 2.163 1.934 2.278a2.163 2.163 0 002.386-2.15V9.179h4.851L10 2.163 2.989 9.179z',\n  down: 'M10 1a3.966 3.966 0 013.96 3.962V9.02h3.202c.706 0 1.335.42 1.605 1.073.27.652.122 1.396-.377 1.895l-7.754 7.759a.925.925 0 01-1.272 0l-7.754-7.76a1.734 1.734 0 01-.376-1.894c.27-.652.9-1.073 1.605-1.073h3.202V4.962A3.965 3.965 0 0110 1zm7.01 9.82h-4.85V5.09c0-1.13-.81-2.163-1.934-2.278a2.163 2.163 0 00-2.386 2.15v5.859H2.989l7.01 7.016 7.012-7.016z',\n  collapse: 'M10 2.8A7.2 7.2 0 112.8 10 7.208 7.208 0 0110 2.8zM10 1a9 9 0 100 18 9 9 0 000-18zm4 8.1H6v1.8h8V9.1z',\n  caret: 'M10 13.7a.897.897 0 01-.636-.264l-4.6-4.6a.9.9 0 111.272-1.273L10 11.526l3.964-3.963a.9.9 0 011.272 1.273l-4.6 4.6A.897.897 0 0110 13.7z'\n};\nfunction icon(el, name) {\n  el.innerHTML = \"\u003csvg viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'\u003e\u003cpath d='\" + ICON[name] + \"'\u003e\u003c/path\u003e\u003c/svg\u003e\";\n}\n\nvar head = document.getElementById('head');\nif (!document.getElementById('sub').textContent.trim() \u0026\u0026 !document.getElementById('title').textContent.trim()) {\n  head.style.display = 'none';\n} else {\n  if (!document.getElementById('sub').textContent.trim()) document.getElementById('sub').style.display = 'none';\n  if (!document.getElementById('title').textContent.trim()) document.getElementById('title').style.display = 'none';\n}\n\n// Drop the comments that were left empty, so two- and one-comment threads\n// keep the same vertical rhythm.\nvar all = [].slice.call(document.querySelectorAll('.c'));\nvar cs = all.filter(function (c, i) {\n  var keep = i === 0 || c.querySelector('.text').textContent.trim() !== '';\n  if (!keep) c.remove();\n  return keep;\n});\n\ncs.forEach(function (c, i) {\n  var depth = Math.max(0, Math.min(4, Number(c.dataset.depth) || 0));\n  c.dataset.d = depth;\n  c.style.marginLeft = depth * 56 + 'px';\n  c.style.marginTop = i ? '20px' : '0';\n\n  // A truthy op value is the OP badge; anything empty/false/0/no hides it.\n  var op = String(c.dataset.op || '').trim().toLowerCase();\n  var isOp = op !== '' \u0026\u0026 op !== 'false' \u0026\u0026 op !== '0' \u0026\u0026 op !== 'no';\n  c.querySelector('.op').textContent = isOp ? 'OP' : '';\n\n  icon(c.querySelector('.up'), 'up');\n  icon(c.querySelector('.down'), 'down');\n  icon(c.querySelector('.cl'), 'collapse');\n\n  // No avatar URL: a flat circle with the author's initial, hued from the\n  // name so the same user keeps the same colour across renders.\n  var av = c.querySelector('.av'), name = av.dataset.name || '';\n  if (!av.querySelector('img')) {\n    var h = 0;\n    for (var k = 0; k \u003c name.length; k++) h = (h * 31 + name.charCodeAt(k)) % 360;\n    av.style.background = 'linear-gradient(150deg,hsl(' + h + ' 62% 72%),hsl(' + ((h + 40) % 360) + ' 58% 52%))';\n    av.textContent = (name.trim()[0] || '?').toUpperCase();\n  }\n\n});\n\nfunction connect(parent, child) {\n  parent.querySelector('.tl').style.display = 'block';\n  var conn = document.createElement('div');\n  conn.className = 'conn';\n  conn.style.display = 'block';\n  child.appendChild(conn);\n}\n\n// A deeper comment below means this one has replies: run the threadline down\n// its rail, put the collapse dot on it and curve into the reply — the same\n// three marks Reddit draws.\ncs.forEach(function (c, i) {\n  var prev = cs[i - 1];\n  if (prev \u0026\u0026 Number(c.dataset.d) \u003e Number(prev.dataset.d)) {\n    connect(prev, c);\n    prev.querySelector('.cl').style.display = 'block';\n  }\n});\n\n// The \"N more replies\" pill belongs one level in from the last comment, hung\n// off its rail. Reddit shows no collapse dot for collapsed replies, only the\n// curve — so this one does not set .cl.\nvar more = document.getElementById('more');\nif (more.textContent.trim()) {\n  var last = cs[cs.length - 1];\n  more.style.marginLeft = (Number(last.dataset.d) + 1) * 56 + 'px';\n  more.style.marginTop = '20px';\n  connect(last, more);\n} else {\n  more.remove();\n}\n\n// Never clip: a thread taller than the canvas shrinks until it fits. zoom,\n// not transform — transform leaves the layout box at full height, which\n// pushes the header off the top of a centred page. Each pass reflows, so\n// converge instead of trusting the first ratio.\nvar thread = document.getElementById('thread');\nfunction room() {\n  return document.documentElement.clientHeight - 80 -\n    (head.style.display === 'none' ? 0 : head.offsetHeight + 34);\n}\nfor (var z = 1, n = 0; n \u003c 8 \u0026\u0026 z \u003e 0.62; n++) {\n  var h = thread.getBoundingClientRect().height;\n  if (h \u003c= room()) break;\n  z = Math.max(0.6, z * Math.min(0.97, room() / h));\n  thread.style.zoom = z;\n}\n\u003c/script\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n","params":{"c1_author":{"type":"string","maxLen":40,"description":"Top comment's author username, without the u/ prefix."},"c1_avatar_url":{"type":"image_url","maxLen":500,"description":"Optional http(s) avatar image, fetched server-side. Without it the avatar is a coloured circle with the name's initial."},"c1_op":{"type":"string","maxLen":5,"description":"Any non-empty value except false/0/no shows the blue OP badge after the name."},"c1_score":{"type":"string","maxLen":10,"description":"Score between the vote arrows, e.g. '5' or '1.2k'."},"c1_text":{"type":"string","maxLen":700,"description":"The top comment's body. Newlines are kept; the body clamps at 8 lines."},"c1_time":{"type":"string","maxLen":20,"description":"Relative age next to the name, e.g. '6mo ago' or '14h ago'."},"c2_author":{"type":"string","maxLen":40,"description":"Second comment's author username, without the u/ prefix."},"c2_avatar_url":{"type":"image_url","maxLen":500,"description":"Optional http(s) avatar image, fetched server-side. Without it the avatar is a coloured circle with the name's initial."},"c2_depth":{"type":"number","default":"1","maxLen":2,"description":"Indent level of the second comment: 0 = another top-level comment, 1 = a reply to the first."},"c2_op":{"type":"string","maxLen":5,"description":"Any non-empty value except false/0/no shows the blue OP badge after the name."},"c2_score":{"type":"string","maxLen":10,"description":"Score between the vote arrows, e.g. '5' or '1.2k'."},"c2_text":{"type":"string","maxLen":700,"description":"The second comment's body; empty drops the comment. Newlines are kept; the body clamps at 8 lines."},"c2_time":{"type":"string","maxLen":20,"description":"Relative age next to the name, e.g. '6mo ago' or '14h ago'."},"c3_author":{"type":"string","maxLen":40,"description":"Third comment's author username, without the u/ prefix."},"c3_avatar_url":{"type":"image_url","maxLen":500,"description":"Optional http(s) avatar image, fetched server-side. Without it the avatar is a coloured circle with the name's initial."},"c3_depth":{"type":"number","default":"1","maxLen":2,"description":"Indent level of the third comment, 0-4. Going deeper than the comment above it draws that comment's threadline and collapse dot."},"c3_op":{"type":"string","maxLen":5,"description":"Any non-empty value except false/0/no shows the blue OP badge after the name."},"c3_score":{"type":"string","maxLen":10,"description":"Score between the vote arrows, e.g. '5' or '1.2k'."},"c3_text":{"type":"string","maxLen":700,"description":"The third comment's body; empty drops the comment. Newlines are kept; the body clamps at 8 lines."},"c3_time":{"type":"string","maxLen":20,"description":"Relative age next to the name, e.g. '6mo ago' or '14h ago'."},"more_replies":{"type":"string","maxLen":30,"description":"Optional pill under the thread, e.g. '2 more replies'. Rendered one level deeper than the last comment. Empty hides it."},"subreddit":{"type":"string","maxLen":40,"description":"Subreddit line above the thread, e.g. 'r/qrcode'. Empty hides it."},"title":{"type":"string","maxLen":160,"description":"Post title above the thread, clamped to two lines. Empty hides it; empty together with subreddit drops the header entirely."}}}}
