[{"data":1,"prerenderedAt":740},["ShallowReactive",2],{"guide-rot13-spoiler-obfuscation":3},{"id":4,"title":5,"body":6,"date":732,"description":733,"extension":734,"meta":735,"navigation":368,"path":736,"readingTime":359,"seo":737,"stem":738,"__hash__":739},"guides\u002Fguides\u002Frot13-spoiler-obfuscation.md","ROT13 for Spoiler Obfuscation",{"type":7,"value":8,"toc":725},"minimark",[9,13,18,21,24,27,56,60,63,74,84,90,96,100,103,204,531,534,538,541,669,676,680,683,710,713,721],[10,11,12],"p",{},"If you have ever seen a Reddit comment that looks like \"Gur fvta vf n yvggyr abyr\" and known you coulddecode itt if you wanted to, you have encountered ROT13 spoiler obfuscation. The practice of encoding spoilers with ROT13 is one of the oldest internet conventions still in active use — a social contract where writers hide content and readers choose whether to reveal it. This guide covers the history, conventions, and practical implementation of ROT13 spoiler protection.",[14,15,17],"h2",{"id":16},"the-usenet-origins","The Usenet Origins",[10,19,20],{},"ROT13 spoiler obfuscation began on Usenet in the early 1980s. Usenet was a text-only discussion system organized into newsgroups, and posts were downloaded in bulk by newsreader clients. There was no \"click to reveal\" UI — every character of every post was visible immediately.",[10,22,23],{},"The problem was obvious: in a newsgroup discussing a movie or book, how do you talk about the ending without ruining it for people scrolling past? The solution was ROT13. Writers encoded the spoilery portion, and readers decoded it intentionally using a built-in newsreader function.",[10,25,26],{},"Key aspects of the Usenet convention:",[28,29,30,38,44,50],"ul",{},[31,32,33,37],"li",{},[34,35,36],"strong",{},"Only the spoilery portion was encoded",", not the entire message. A post might say \"I loved the cinematography. The ending was a surprise: \" followed by the ROT13-encoded twist.",[31,39,40,43],{},[34,41,42],{},"The encoding was signaled explicitly",". Writers prefaced encoded text with phrases like \"ROT13 for spoilers:\" or \"Spoiler space below.\"",[31,45,46,49],{},[34,47,48],{},"Newsreader software included one-key ROT13 toggle",". Trn, rn, slrn, and other popular readers had a single keystroke to decode the current article.",[31,51,52,55],{},[34,53,54],{},"The convention was self-enforcing",". Communities on rec.arts.movies and similar groups reinforced the practice through social norms, not technology.",[14,57,59],{"id":58},"modern-platform-usage","Modern Platform Usage",[10,61,62],{},"The ROT13 convention migrated from Usenet to web forums, mailing lists, and eventually social media platforms. Current usage patterns:",[10,64,65,68,69,73],{},[34,66,67],{},"Reddit",": Some subreddits still use ROT13 for spoilers in comments, though Reddit now has a native spoiler syntax (",[70,71,72],"code",{},">!spoiler!>","). ROT13 persists in communities where the native spoiler tag is not supported or as a cultural artifact.",[10,75,76,79,80,83],{},[34,77,78],{},"Discord",": Users encode spoilers with ROT13 in channels where the ",[70,81,82],{},"\u002Fspoiler"," markup is unavailable or when they want the spoiler to be slightly harder to reveal accidentally.",[10,85,86,89],{},[34,87,88],{},"Email lists",": Technical mailing lists sometimes use ROT13 for solutions to puzzles or answers to interview questions, since email clients lack native spoiler support.",[10,91,92,95],{},[34,93,94],{},"Puzzle and riddle sites",": ARGs (alternate reality games) and puzzle hunts commonly use ROT13 as a basic encoding layer. It is intentionally easy to decode, making it a gateway cipher that introduces participants to more complex encodings.",[14,97,99],{"id":98},"implementing-rot13-spoiler-tags","Implementing ROT13 Spoiler Tags",[10,101,102],{},"For web developers building community features, ROT13 spoiler support is straightforward to implement:",[104,105,110],"pre",{"className":106,"code":107,"language":108,"meta":109,"style":109},"language-html shiki shiki-themes github-light github-dark","\u003Cdiv class=\"spoiler-rot13\" data-encoded=\"Gur orvat vf nyernql ubyr\">\n  \u003Cbutton class=\"spoiler-toggle\">Reveal spoiler\u003C\u002Fbutton>\n  \u003Cspan class=\"spoiler-text\" hidden>\u003C\u002Fspan>\n\u003C\u002Fdiv>\n","html","",[70,111,112,147,170,194],{"__ignoreMap":109},[113,114,117,121,125,129,132,136,139,141,144],"span",{"class":115,"line":116},"line",1,[113,118,120],{"class":119},"sVt8B","\u003C",[113,122,124],{"class":123},"s9eBZ","div",[113,126,128],{"class":127},"sScJk"," class",[113,130,131],{"class":119},"=",[113,133,135],{"class":134},"sZZnC","\"spoiler-rot13\"",[113,137,138],{"class":127}," data-encoded",[113,140,131],{"class":119},[113,142,143],{"class":134},"\"Gur orvat vf nyernql ubyr\"",[113,145,146],{"class":119},">\n",[113,148,150,153,156,158,160,163,166,168],{"class":115,"line":149},2,[113,151,152],{"class":119},"  \u003C",[113,154,155],{"class":123},"button",[113,157,128],{"class":127},[113,159,131],{"class":119},[113,161,162],{"class":134},"\"spoiler-toggle\"",[113,164,165],{"class":119},">Reveal spoiler\u003C\u002F",[113,167,155],{"class":123},[113,169,146],{"class":119},[113,171,173,175,177,179,181,184,187,190,192],{"class":115,"line":172},3,[113,174,152],{"class":119},[113,176,113],{"class":123},[113,178,128],{"class":127},[113,180,131],{"class":119},[113,182,183],{"class":134},"\"spoiler-text\"",[113,185,186],{"class":127}," hidden",[113,188,189],{"class":119},">\u003C\u002F",[113,191,113],{"class":123},[113,193,146],{"class":119},[113,195,197,200,202],{"class":115,"line":196},4,[113,198,199],{"class":119},"\u003C\u002F",[113,201,124],{"class":123},[113,203,146],{"class":119},[104,205,209],{"className":206,"code":207,"language":208,"meta":109,"style":109},"language-javascript shiki shiki-themes github-light github-dark","function rot13(text) {\n  return text.replace(\u002F[a-zA-Z]\u002Fg, char => {\n    const base = char \u003C= 'Z' ? 65 : 97\n    return String.fromCharCode(((char.charCodeAt(0) - base + 13) % 26) + base)\n  })\n}\n\ndocument.querySelectorAll('.spoiler-rot13').forEach(el => {\n  const button = el.querySelector('.spoiler-toggle')\n  const span = el.querySelector('.spoiler-text')\n  const encoded = el.dataset.encoded\n\n  button.addEventListener('click', () => {\n    span.textContent = rot13(encoded)\n    span.hidden = false\n    button.hidden = true\n  })\n})\n","javascript",[70,210,211,230,267,299,351,357,363,370,399,424,445,458,463,485,498,509,520,525],{"__ignoreMap":109},[113,212,213,217,220,223,227],{"class":115,"line":116},[113,214,216],{"class":215},"szBVR","function",[113,218,219],{"class":127}," rot13",[113,221,222],{"class":119},"(",[113,224,226],{"class":225},"s4XuR","text",[113,228,229],{"class":119},") {\n",[113,231,232,235,238,241,243,246,250,252,255,258,261,264],{"class":115,"line":149},[113,233,234],{"class":215},"  return",[113,236,237],{"class":119}," text.",[113,239,240],{"class":127},"replace",[113,242,222],{"class":119},[113,244,245],{"class":134},"\u002F",[113,247,249],{"class":248},"sj4cs","[a-zA-Z]",[113,251,245],{"class":134},[113,253,254],{"class":215},"g",[113,256,257],{"class":119},", ",[113,259,260],{"class":225},"char",[113,262,263],{"class":215}," =>",[113,265,266],{"class":119}," {\n",[113,268,269,272,275,278,281,284,287,290,293,296],{"class":115,"line":172},[113,270,271],{"class":215},"    const",[113,273,274],{"class":248}," base",[113,276,277],{"class":215}," =",[113,279,280],{"class":119}," char ",[113,282,283],{"class":215},"\u003C=",[113,285,286],{"class":134}," 'Z'",[113,288,289],{"class":215}," ?",[113,291,292],{"class":248}," 65",[113,294,295],{"class":215}," :",[113,297,298],{"class":248}," 97\n",[113,300,301,304,307,310,313,316,318,321,324,327,330,333,336,338,341,344,346,348],{"class":115,"line":196},[113,302,303],{"class":215},"    return",[113,305,306],{"class":119}," String.",[113,308,309],{"class":127},"fromCharCode",[113,311,312],{"class":119},"(((char.",[113,314,315],{"class":127},"charCodeAt",[113,317,222],{"class":119},[113,319,320],{"class":248},"0",[113,322,323],{"class":119},") ",[113,325,326],{"class":215},"-",[113,328,329],{"class":119}," base ",[113,331,332],{"class":215},"+",[113,334,335],{"class":248}," 13",[113,337,323],{"class":119},[113,339,340],{"class":215},"%",[113,342,343],{"class":248}," 26",[113,345,323],{"class":119},[113,347,332],{"class":215},[113,349,350],{"class":119}," base)\n",[113,352,354],{"class":115,"line":353},5,[113,355,356],{"class":119},"  })\n",[113,358,360],{"class":115,"line":359},6,[113,361,362],{"class":119},"}\n",[113,364,366],{"class":115,"line":365},7,[113,367,369],{"emptyLinePlaceholder":368},true,"\n",[113,371,373,376,379,381,384,387,390,392,395,397],{"class":115,"line":372},8,[113,374,375],{"class":119},"document.",[113,377,378],{"class":127},"querySelectorAll",[113,380,222],{"class":119},[113,382,383],{"class":134},"'.spoiler-rot13'",[113,385,386],{"class":119},").",[113,388,389],{"class":127},"forEach",[113,391,222],{"class":119},[113,393,394],{"class":225},"el",[113,396,263],{"class":215},[113,398,266],{"class":119},[113,400,402,405,408,410,413,416,418,421],{"class":115,"line":401},9,[113,403,404],{"class":215},"  const",[113,406,407],{"class":248}," button",[113,409,277],{"class":215},[113,411,412],{"class":119}," el.",[113,414,415],{"class":127},"querySelector",[113,417,222],{"class":119},[113,419,420],{"class":134},"'.spoiler-toggle'",[113,422,423],{"class":119},")\n",[113,425,427,429,432,434,436,438,440,443],{"class":115,"line":426},10,[113,428,404],{"class":215},[113,430,431],{"class":248}," span",[113,433,277],{"class":215},[113,435,412],{"class":119},[113,437,415],{"class":127},[113,439,222],{"class":119},[113,441,442],{"class":134},"'.spoiler-text'",[113,444,423],{"class":119},[113,446,448,450,453,455],{"class":115,"line":447},11,[113,449,404],{"class":215},[113,451,452],{"class":248}," encoded",[113,454,277],{"class":215},[113,456,457],{"class":119}," el.dataset.encoded\n",[113,459,461],{"class":115,"line":460},12,[113,462,369],{"emptyLinePlaceholder":368},[113,464,466,469,472,474,477,480,483],{"class":115,"line":465},13,[113,467,468],{"class":119},"  button.",[113,470,471],{"class":127},"addEventListener",[113,473,222],{"class":119},[113,475,476],{"class":134},"'click'",[113,478,479],{"class":119},", () ",[113,481,482],{"class":215},"=>",[113,484,266],{"class":119},[113,486,488,491,493,495],{"class":115,"line":487},14,[113,489,490],{"class":119},"    span.textContent ",[113,492,131],{"class":215},[113,494,219],{"class":127},[113,496,497],{"class":119},"(encoded)\n",[113,499,501,504,506],{"class":115,"line":500},15,[113,502,503],{"class":119},"    span.hidden ",[113,505,131],{"class":215},[113,507,508],{"class":248}," false\n",[113,510,512,515,517],{"class":115,"line":511},16,[113,513,514],{"class":119},"    button.hidden ",[113,516,131],{"class":215},[113,518,519],{"class":248}," true\n",[113,521,523],{"class":115,"line":522},17,[113,524,356],{"class":119},[113,526,528],{"class":115,"line":527},18,[113,529,530],{"class":119},"})\n",[10,532,533],{},"This approach stores the ROT13-encoded text in a data attribute and decodes it on the client when the user clicks. The spoiler text is never present in the DOM in plaintext until the user explicitly requests it — important for screen scrapers and search indexes that might otherwise surface the spoiler.",[14,535,537],{"id":536},"server-side-encoding","Server-Side Encoding",[10,539,540],{},"For content management systems, encode spoilers at write time so the plaintext never reaches the database:",[104,542,544],{"className":206,"code":543,"language":208,"meta":109,"style":109},"function encodeSpoilers(content) {\n  return content.replace(\n    \u002F\\[spoiler\\](.*?)\\[\\\u002Fspoiler\\]\u002Fgs,\n    (_, text) => `[spoiler-rot13]${rot13(text)}[\u002Fspoiler-rot13]`\n  )\n}\n\n\u002F\u002F Input: \"The killer was [spoiler]the butler[\u002Fspoiler]\"\n\u002F\u002F Output: \"The killer was [spoiler-rot13]gur ohgyre[\u002Fspoiler-rot13]\"\n",[70,545,546,560,572,614,645,650,654,658,664],{"__ignoreMap":109},[113,547,548,550,553,555,558],{"class":115,"line":116},[113,549,216],{"class":215},[113,551,552],{"class":127}," encodeSpoilers",[113,554,222],{"class":119},[113,556,557],{"class":225},"content",[113,559,229],{"class":119},[113,561,562,564,567,569],{"class":115,"line":149},[113,563,234],{"class":215},[113,565,566],{"class":119}," content.",[113,568,240],{"class":127},[113,570,571],{"class":119},"(\n",[113,573,574,577,581,585,588,590,593,596,599,602,604,606,608,611],{"class":115,"line":172},[113,575,576],{"class":134},"    \u002F",[113,578,580],{"class":579},"snhLl","\\[",[113,582,584],{"class":583},"sA_wV","spoiler",[113,586,587],{"class":579},"\\]",[113,589,222],{"class":583},[113,591,592],{"class":248},".",[113,594,595],{"class":215},"*?",[113,597,598],{"class":583},")",[113,600,601],{"class":579},"\\[\\\u002F",[113,603,584],{"class":583},[113,605,587],{"class":579},[113,607,245],{"class":134},[113,609,610],{"class":215},"gs",[113,612,613],{"class":119},",\n",[113,615,616,619,622,624,626,628,630,633,636,638,640,642],{"class":115,"line":196},[113,617,618],{"class":119},"    (",[113,620,621],{"class":225},"_",[113,623,257],{"class":119},[113,625,226],{"class":225},[113,627,323],{"class":119},[113,629,482],{"class":215},[113,631,632],{"class":134}," `[spoiler-rot13]${",[113,634,635],{"class":127},"rot13",[113,637,222],{"class":134},[113,639,226],{"class":119},[113,641,598],{"class":134},[113,643,644],{"class":134},"}[\u002Fspoiler-rot13]`\n",[113,646,647],{"class":115,"line":353},[113,648,649],{"class":119},"  )\n",[113,651,652],{"class":115,"line":359},[113,653,362],{"class":119},[113,655,656],{"class":115,"line":365},[113,657,369],{"emptyLinePlaceholder":368},[113,659,660],{"class":115,"line":372},[113,661,663],{"class":662},"sJ8bj","\u002F\u002F Input: \"The killer was [spoiler]the butler[\u002Fspoiler]\"\n",[113,665,666],{"class":115,"line":401},[113,667,668],{"class":662},"\u002F\u002F Output: \"The killer was [spoiler-rot13]gur ohgyre[\u002Fspoiler-rot13]\"\n",[10,670,671,672,675],{},"This pattern lets authors write in a natural ",[70,673,674],{},"[spoiler]...[\u002Fspoiler]"," syntax while the storage layer automatically converts to ROT13. The rendering layer then handles the decode-on-click interaction.",[14,677,679],{"id":678},"accessibility-considerations","Accessibility Considerations",[10,681,682],{},"ROT13 spoilers create tension with accessibility:",[28,684,685,698,704],{},[31,686,687,690,691,694,695,592],{},[34,688,689],{},"Screen readers"," read the raw encoded text aloud, which is gibberish. Use ",[70,692,693],{},"aria-label"," to provide context: ",[70,696,697],{},"\u003Cspan aria-label=\"Spoiler hidden, click to reveal\">Gur ohgyre\u003C\u002Fspan>",[31,699,700,703],{},[34,701,702],{},"Keyboard navigation"," users need a way to reveal the spoiler without a mouse. The toggle button should be focusable and activatable with Enter or Space.",[31,705,706,709],{},[34,707,708],{},"Copy-paste"," should copy the encoded text, not the decoded version, until the user explicitly reveals it. This prevents accidental sharing of spoilers through quoting.",[10,711,712],{},"The social contract of ROT13 — that readers opt in to seeing the content — only works when the reveal mechanism is genuinely voluntary. Auto-decoding on hover or on page load violates that contract.",[10,714,715,716,720],{},"Encode and decode text with ROT13 at ",[717,718,719],"a",{"href":719},"\u002Ftools\u002Frot13",", which provides instant bidirectional conversion for spoiler text, puzzle hints, and any content where the reader should choose whether to see it.",[722,723,724],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .snhLl, html code.shiki .snhLl{--shiki-default:#22863A;--shiki-default-font-weight:bold;--shiki-dark:#85E89D;--shiki-dark-font-weight:bold}html pre.shiki code .sA_wV, html code.shiki .sA_wV{--shiki-default:#032F62;--shiki-dark:#DBEDFF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":109,"searchDepth":149,"depth":149,"links":726},[727,728,729,730,731],{"id":16,"depth":149,"text":17},{"id":58,"depth":149,"text":59},{"id":98,"depth":149,"text":99},{"id":536,"depth":149,"text":537},{"id":678,"depth":149,"text":679},"2026-05-28","The tradition of using ROT13 to hide spoilers on Usenet, Reddit, and web forums.","md",{},"\u002Fguides\u002Frot13-spoiler-obfuscation",{"title":5,"description":733},"guides\u002Frot13-spoiler-obfuscation","swatL9cRaDCsE-bz-KZUMg9gJv0Y8YsWb_fIY1-L8GM",1780401336625]