<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: visiohex</title>
    <description>The latest articles on DEV Community by visiohex (@jy_wang_2c4d36f4d66adea80).</description>
    <link>https://dev.to/jy_wang_2c4d36f4d66adea80</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3765913%2F6890e795-c29f-462e-a420-54ad45b80086.png</url>
      <title>DEV Community: visiohex</title>
      <link>https://dev.to/jy_wang_2c4d36f4d66adea80</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jy_wang_2c4d36f4d66adea80"/>
    <language>en</language>
    <item>
      <title>Building a private tarot calculator with Astro and zero backend</title>
      <dc:creator>visiohex</dc:creator>
      <pubDate>Fri, 07 Aug 2026 08:45:06 +0000</pubDate>
      <link>https://dev.to/jy_wang_2c4d36f4d66adea80/building-a-private-tarot-calculator-with-astro-and-zero-backend-14e2</link>
      <guid>https://dev.to/jy_wang_2c4d36f4d66adea80/building-a-private-tarot-calculator-with-astro-and-zero-backend-14e2</guid>
      <description>&lt;p&gt;I recently built &lt;a href="https://meuarcanopessoal.net/" rel="noopener noreferrer"&gt;Meu Arcano Pessoal&lt;/a&gt;, a tarot calculator for Brazilian Portuguese users.&lt;/p&gt;

&lt;p&gt;The site calculates a personal Major Arcana card from a birth date, with an optional name-based method. It also includes an annual Arcana calculator, relationship compatibility readings, and pages explaining all 22 Major Arcana cards.&lt;/p&gt;

&lt;p&gt;The subject is tarot, but the project involved familiar web development questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How can an interactive calculator work without a backend?&lt;/li&gt;
&lt;li&gt;How should personal inputs be handled without collecting them?&lt;/li&gt;
&lt;li&gt;How can one calculator grow into a useful content site?&lt;/li&gt;
&lt;li&gt;How do you present symbolic results without making false predictions?&lt;/li&gt;
&lt;li&gt;How much JavaScript does the site need?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I built it with Astro, TypeScript, browser APIs, and Cloudflare Pages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I kept the calculation in the browser
&lt;/h2&gt;

&lt;p&gt;The calculator asks for a birth date. One optional calculation method can also use a name.&lt;/p&gt;

&lt;p&gt;Neither input needs to leave the device. A server would add complexity and create a privacy problem without improving the result.&lt;/p&gt;

&lt;p&gt;The calculation follows this flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The user enters a date.&lt;/li&gt;
&lt;li&gt;The browser validates it.&lt;/li&gt;
&lt;li&gt;TypeScript calculates a number between 1 and 22.&lt;/li&gt;
&lt;li&gt;The corresponding Arcana appears on the page.&lt;/li&gt;
&lt;li&gt;The input disappears when the page closes or reloads.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The site does not place birth dates or names in URLs, &lt;code&gt;localStorage&lt;/code&gt;, cookies, analytics events, or network requests.&lt;/p&gt;

&lt;p&gt;There is no account system, database, API route, or serverless function to maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  The reduction function
&lt;/h2&gt;

&lt;p&gt;The site maps a number to one of the 22 Major Arcana cards. When a total is greater than 22, the calculator adds its digits again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;reduceToArcano&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;digit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;digit&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a personal Arcana calculation, the site adds the digits in the birth date before applying the reduction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;calculatePersonalArcano&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;digits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDate&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMonth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFullYear&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;digit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;digit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;reduceToArcano&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Input validation happens before this function runs. The form rejects impossible dates and future dates instead of letting JavaScript normalize them into another day.&lt;/p&gt;

&lt;p&gt;For example, JavaScript can turn April 31 into a date in May. The calculator should not produce a result from a date the user never entered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling tarot numbering differences
&lt;/h2&gt;

&lt;p&gt;Tarot decks do not all use the same numbering.&lt;/p&gt;

&lt;p&gt;In the Rider-Waite system, Strength is number 8 and Justice is number 11. Some traditions reverse those numbers. The Fool may appear as 0, 22, or remain unnumbered.&lt;/p&gt;

&lt;p&gt;The site uses this sequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;8  A Força
11 A Justiça
22 O Louco
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I documented this decision on the methodology page instead of hiding it inside the calculation.&lt;/p&gt;

&lt;p&gt;Two calculators can receive the same birth date and show different card names because they use different numbering systems. The formula may be working correctly in both cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding a name-based calculation
&lt;/h2&gt;

&lt;p&gt;The optional name method uses a Pythagorean letter mapping.&lt;/p&gt;

&lt;p&gt;Before assigning values, the browser normalizes the input:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;normalizeName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NFD&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[\u&lt;/span&gt;&lt;span class="sr"&gt;0300-&lt;/span&gt;&lt;span class="se"&gt;\u&lt;/span&gt;&lt;span class="sr"&gt;036f&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/ç/gi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;c&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[^&lt;/span&gt;&lt;span class="sr"&gt;a-z&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/gi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lets names containing &lt;code&gt;á&lt;/code&gt;, &lt;code&gt;ã&lt;/code&gt;, &lt;code&gt;ê&lt;/code&gt;, or &lt;code&gt;ç&lt;/code&gt; use the same mapping as their base Latin letters.&lt;/p&gt;

&lt;p&gt;I kept the date-only method available because asking for a full name creates friction and changes the interpretation. The interface explains what each method uses before the user chooses one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Annual Arcana without requesting a birth year
&lt;/h2&gt;

&lt;p&gt;The annual calculator needs a birth day, birth month, and target year. It does not need the user’s birth year.&lt;/p&gt;

&lt;p&gt;Requesting the full birth date would collect an extra piece of personal information for no reason.&lt;/p&gt;

&lt;p&gt;The calculation looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;calculateAnnualArcano&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;day&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;month&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;year&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;reduceToArcano&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;day&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;month&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;year&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A person born on September 14 calculating the result for 2026 gets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;14 + 9 + 2026 = 2049
2 + 0 + 4 + 9 = 15
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result is Arcana 15.&lt;/p&gt;

&lt;p&gt;The annual tool supports 2026 and 2027. Each result includes prompts for relationships, work, change, and one practical action. It does not claim to predict a specific event.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compatibility without fake precision
&lt;/h2&gt;

&lt;p&gt;Relationship calculators often return scores such as “87% compatible.” I did not have a defensible way to calculate that number, so this tool does not produce one.&lt;/p&gt;

&lt;p&gt;The compatibility page calculates two individual Arcana cards and describes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what each person may bring to the relationship;&lt;/li&gt;
&lt;li&gt;differences in communication;&lt;/li&gt;
&lt;li&gt;what each person may need;&lt;/li&gt;
&lt;li&gt;likely points of friction;&lt;/li&gt;
&lt;li&gt;a question or exercise they can try together.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Users can select love, friendship, or work as the context.&lt;/p&gt;

&lt;p&gt;Swapping the two birth dates changes the display order but not the shared interpretation. Equal Arcana results receive a combined explanation so the page does not repeat the same paragraph twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  One data source for 22 pages
&lt;/h2&gt;

&lt;p&gt;Each Arcana lives in a typed data object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Arcano&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;love&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;work&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;strengths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;cautions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;related&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Astro uses this data to generate the 22 detail pages at build time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getStaticPaths&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;arcanos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;arcano&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;arcano&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;arcano&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;arcano&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser downloads no card database after the page loads. Search engines and visitors receive complete HTML.&lt;/p&gt;

&lt;p&gt;The same data powers the calculator results, card directory, related-card links, metadata, and share images. Keeping those features attached to one source prevents card names and descriptions from drifting between pages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating share cards with Canvas
&lt;/h2&gt;

&lt;p&gt;Users can export their result as a 1080 by 1350 image. That size works well for vertical social posts.&lt;/p&gt;

&lt;p&gt;The browser draws the card with the Canvas API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;canvas&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1350&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2d&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Canvas is not available&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fillStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#0c0618&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fillStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#ead08b&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;font&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;48px serif&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The complete version also loads the Arcana image and adds a short meaning.&lt;/p&gt;

&lt;p&gt;If the browser supports file sharing, the site uses the Web Share API. Otherwise, it downloads the PNG.&lt;/p&gt;

&lt;p&gt;The image contains the result, but no name or birth date.&lt;/p&gt;

&lt;h2&gt;
  
  
  Analytics without personal inputs
&lt;/h2&gt;

&lt;p&gt;I use Plausible to record a few product events:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;calculo_concluido
resultado_aberto
compartilhamento
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Events can include fixed categories such as the tool name, selected year, relationship mode, or sharing method.&lt;/p&gt;

&lt;p&gt;They do not include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;names;&lt;/li&gt;
&lt;li&gt;birth dates;&lt;/li&gt;
&lt;li&gt;Arcana numbers;&lt;/li&gt;
&lt;li&gt;generated interpretations;&lt;/li&gt;
&lt;li&gt;form values.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I want to know whether visitors finish a calculation and whether the sharing tool works. I do not need to know what they entered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Static deployment on Cloudflare
&lt;/h2&gt;

&lt;p&gt;Astro builds the site into static files. Cloudflare Pages serves those files behind the production domain.&lt;/p&gt;

&lt;p&gt;The deployment has no runtime application server. Cloudflare handles HTTPS, caching, DNS, response headers, and the redirect from &lt;code&gt;www&lt;/code&gt; to the root domain.&lt;/p&gt;

&lt;p&gt;The site also includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;canonical URLs;&lt;/li&gt;
&lt;li&gt;Open Graph metadata;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;robots.txt&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sitemap.xml&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;breadcrumb structured data;&lt;/li&gt;
&lt;li&gt;FAQ structured data where the questions are visible;&lt;/li&gt;
&lt;li&gt;an &lt;code&gt;llms.txt&lt;/code&gt; file;&lt;/li&gt;
&lt;li&gt;a privacy policy and methodology page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Static generation does not remove the need for JavaScript. It limits JavaScript to the places where it earns its bytes: calculators, search, result interaction, and image sharing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that took longer than the calculator
&lt;/h2&gt;

&lt;p&gt;The formula was a small part of the project.&lt;/p&gt;

&lt;p&gt;Writing useful interpretations for 22 cards took longer. Each page needed to answer the searches that brought someone there, including &lt;code&gt;arcano 11&lt;/code&gt;, &lt;code&gt;arcano pessoal 17&lt;/code&gt;, and &lt;code&gt;significado do arcano 18&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The card illustrations also needed review. A polished image can still be wrong if it misses the symbols that identify a Rider-Waite card. I checked the characters, posture, objects, animals, background elements, and card-specific composition instead of treating the images as decoration.&lt;/p&gt;

&lt;p&gt;The methodology page became another important part of the site. It explains why calculators disagree, how names are normalized, which numbering system the site uses, and what to do when an interpretation does not fit.&lt;/p&gt;

&lt;p&gt;Those answers help users more than another paragraph filled with broad spiritual language.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would build next
&lt;/h2&gt;

&lt;p&gt;I plan to watch which tools people use before adding more pages.&lt;/p&gt;

&lt;p&gt;The current site covers personal, annual, and relationship calculations. A future addition should answer a recurring user question or improve an existing result. Publishing hundreds of thin combinations would increase the page count without making the site better.&lt;/p&gt;

&lt;p&gt;You can try the project here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://meuarcanopessoal.net/" rel="noopener noreferrer"&gt;https://meuarcanopessoal.net/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The methodology and formulas are available here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://meuarcanopessoal.net/metodologia/" rel="noopener noreferrer"&gt;https://meuarcanopessoal.net/metodologia/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>sideprojects</category>
      <category>astro</category>
    </item>
    <item>
      <title>How I Built a Source-Constrained Game Guide with Astro and Cloudflare Pages</title>
      <dc:creator>visiohex</dc:creator>
      <pubDate>Fri, 07 Aug 2026 08:29:17 +0000</pubDate>
      <link>https://dev.to/jy_wang_2c4d36f4d66adea80/how-i-built-a-source-constrained-game-guide-with-astro-and-cloudflare-pages-2o37</link>
      <guid>https://dev.to/jy_wang_2c4d36f4d66adea80/how-i-built-a-source-constrained-game-guide-with-astro-and-cloudflare-pages-2o37</guid>
      <description>&lt;p&gt;I recently built &lt;a href="https://ballscratchsimulator.wiki/" rel="noopener noreferrer"&gt;Ball Scratch Simulator Guide&lt;/a&gt;, an independent information site for an upcoming Steam game from 10ft Games.&lt;/p&gt;

&lt;p&gt;The technical work was straightforward. The difficult part was deciding what the site should say when the game has not been released and much of the information people search for does not exist yet.&lt;/p&gt;

&lt;p&gt;Instead of filling those gaps with guesses, I built the site around a simple editorial constraint:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Every factual claim must be traceable to an official public source. Anything that has not been announced must be described as unknown.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That decision shaped the content model, page structure, SEO setup, and deployment architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with covering an unreleased game
&lt;/h2&gt;

&lt;p&gt;Searches for an upcoming game often appear before enough information exists to support a conventional guide.&lt;/p&gt;

&lt;p&gt;Players may already be looking for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The release date&lt;/li&gt;
&lt;li&gt;The price&lt;/li&gt;
&lt;li&gt;A playable demo&lt;/li&gt;
&lt;li&gt;Controls&lt;/li&gt;
&lt;li&gt;VR support&lt;/li&gt;
&lt;li&gt;System requirements&lt;/li&gt;
&lt;li&gt;Locations&lt;/li&gt;
&lt;li&gt;Collectibles&lt;/li&gt;
&lt;li&gt;Achievements&lt;/li&gt;
&lt;li&gt;Codes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For Ball Scratch Simulator, the official Steam listing currently confirms several useful details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It is a Windows game developed by 10ft Games.&lt;/li&gt;
&lt;li&gt;Steam lists an August 2026 release window.&lt;/li&gt;
&lt;li&gt;Desktop play is supported.&lt;/li&gt;
&lt;li&gt;Seated PC VR is optional through OpenXR and SteamVR.&lt;/li&gt;
&lt;li&gt;The game includes six public locations.&lt;/li&gt;
&lt;li&gt;Cards, Chibis, high scores, and leaderboards are part of the progression system.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Other details remain unannounced, including the exact release day, launch price, demo availability, location names, and complete controls.&lt;/p&gt;

&lt;p&gt;The site answers those questions directly. “Not announced” is more useful than a fabricated answer hidden inside a long article.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I launched with one indexable page
&lt;/h2&gt;

&lt;p&gt;A common SEO approach is to create a page for every possible query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/release-date/
/codes/
/controls/
/locations/
/collectibles/
/vr/
/system-requirements/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That structure would have created several thin pages containing the same limited facts. Some pages, such as codes or walkthroughs, would have no factual basis at all.&lt;/p&gt;

&lt;p&gt;I chose one indexable homepage that covers the current search intent in depth. It includes release information, gameplay, progression, VR support, PC requirements, languages, content warnings, and frequently asked questions.&lt;/p&gt;

&lt;p&gt;The site also has About, Privacy, Terms, Editorial Policy, and Disclaimer pages. Those pages are useful to visitors, but they are marked &lt;code&gt;noindex,follow&lt;/code&gt; and excluded from the sitemap.&lt;/p&gt;

&lt;p&gt;The sitemap therefore contains one URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;url&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;loc&amp;gt;&lt;/span&gt;https://ballscratchsimulator.wiki/&lt;span class="nt"&gt;&amp;lt;/loc&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/url&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;More pages can be added after the game launches, when there is enough verified information for each page to serve a distinct purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small Astro architecture
&lt;/h2&gt;

&lt;p&gt;The site uses Astro with static output. There is no client-side application framework, CMS, database, or runtime API.&lt;/p&gt;

&lt;p&gt;The core configuration is deliberately small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;astro/config&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;sitemap&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@astrojs/sitemap&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;site&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://ballscratchsimulator.wiki&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;static&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;trailingSlash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;always&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;integrations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;sitemap&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Static output makes the site easy to inspect. The main content, navigation, metadata, and links are present in the generated HTML without requiring JavaScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping game facts in one typed object
&lt;/h2&gt;

&lt;p&gt;Important identity data lives in a typed object instead of being repeated across templates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;game&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GameIdentity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ball Scratch Simulator&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;steamAppId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4951590&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;developer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;10ft Games&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;publisher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;10ft Games&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Windows&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;releaseStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Upcoming&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;releaseWindow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;August 2026&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;verifiedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;August 7, 2026&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;vr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Seated&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;runtimes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OpenXR&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SteamVR&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The page copy, metadata, calls to action, and &lt;code&gt;VideoGame&lt;/code&gt; structured data read from this object.&lt;/p&gt;

&lt;p&gt;This reduces the chance of publishing conflicting release windows or platform information in different parts of the site. When an official detail changes, there is one primary place to update it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Canonical URLs and structured data
&lt;/h2&gt;

&lt;p&gt;Every page receives a self-referencing canonical URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canonical&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Astro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SITE_ORIGIN&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The shared layout also creates Open Graph metadata, Twitter card metadata, &lt;code&gt;WebSite&lt;/code&gt; structured data, and page-specific structured data.&lt;/p&gt;

&lt;p&gt;The homepage includes a &lt;code&gt;VideoGame&lt;/code&gt; JSON-LD object using the same identity data shown to visitors. I avoided adding review scores, prices, release dates, or offers that are not supported by the official sources.&lt;/p&gt;

&lt;p&gt;Structured data should describe the visible page. It should not become a second, more optimistic version of the content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treating official information carefully
&lt;/h2&gt;

&lt;p&gt;Source-constrained publishing still requires judgment.&lt;/p&gt;

&lt;p&gt;The Steam system requirements, for example, currently list GPU recommendations in an order that may look surprising. It would be easy to “correct” them based on an assumption.&lt;/p&gt;

&lt;p&gt;The site displays the values as Steam currently lists them and adds a short note explaining where they came from.&lt;/p&gt;

&lt;p&gt;That preserves the source instead of silently rewriting it.&lt;/p&gt;

&lt;p&gt;The same rule applies to the six confirmed locations. The site can explain that six public settings are planned, but it does not invent names for those locations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing promotional-image cropping
&lt;/h2&gt;

&lt;p&gt;The first version placed official promotional art inside fixed-ratio containers using &lt;code&gt;object-fit: cover&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That worked visually for generic photographs, but it cropped important parts of the game artwork. Titles and characters near the edges disappeared on wider screens.&lt;/p&gt;

&lt;p&gt;The fix was native image sizing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.hero-art&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--line&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Preserving the image’s intrinsic aspect ratio was better than trying to find a universal crop position.&lt;/p&gt;

&lt;p&gt;For artwork containing text, logos, or characters, &lt;code&gt;cover&lt;/code&gt; often solves the container’s problem by creating an image-content problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying to Cloudflare Pages
&lt;/h2&gt;

&lt;p&gt;The generated site is deployed to Cloudflare Pages through Wrangler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run build
npx wrangler pages deploy dist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The custom domain is:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ballscratchsimulator.wiki/" rel="noopener noreferrer"&gt;https://ballscratchsimulator.wiki/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;www&lt;/code&gt; hostname redirects to the root domain with a permanent redirect. Cloudflare also serves the site’s security headers, including a Content Security Policy.&lt;/p&gt;

&lt;p&gt;Because the site includes Google Analytics and Plausible, their external script origins must be explicitly allowed by the CSP. Adding analytics without updating the policy would cause the browser to block the scripts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verification before deployment
&lt;/h2&gt;

&lt;p&gt;The project uses a small verification chain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"check"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"astro check"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node --test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"astro build"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"verify"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm run check &amp;amp;&amp;amp; npm test &amp;amp;&amp;amp; npm run build"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tests check practical publishing requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every planned route exists.&lt;/li&gt;
&lt;li&gt;Each page has one H1.&lt;/li&gt;
&lt;li&gt;The homepage has a valid canonical URL.&lt;/li&gt;
&lt;li&gt;Structured data matches visible game information.&lt;/li&gt;
&lt;li&gt;Trust and legal pages use &lt;code&gt;noindex&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The sitemap contains only the homepage.&lt;/li&gt;
&lt;li&gt;Internal links resolve.&lt;/li&gt;
&lt;li&gt;Core answers remain available without JavaScript.&lt;/li&gt;
&lt;li&gt;Draft instructions and placeholder text do not leak into public pages.&lt;/li&gt;
&lt;li&gt;External scripts are limited to known analytics providers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also check the generated &lt;code&gt;robots.txt&lt;/code&gt;, &lt;code&gt;sitemap.xml&lt;/code&gt;, and &lt;code&gt;llms.txt&lt;/code&gt; instead of assuming the build integration produced the expected files.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;The strongest content decision was allowing “unknown” to be a complete answer.&lt;/p&gt;

&lt;p&gt;If the price has not been announced, the page says so. If no demo is available, it does not offer a fake download button. If location names are unavailable, it does not turn speculation into a list.&lt;/p&gt;

&lt;p&gt;Page count should also follow available evidence. A single useful page is a better starting point than ten pages repeating the same paragraph.&lt;/p&gt;

&lt;p&gt;Static generation made these constraints easier to enforce. The final HTML can be tested directly, and the deployed output has fewer moving parts.&lt;/p&gt;

&lt;p&gt;The site will expand when the game is released and official information supports dedicated guides. Until then, its job is narrower: collect the confirmed details, answer current questions, and make the boundaries clear.&lt;/p&gt;

&lt;p&gt;You can see the finished site at &lt;a href="https://ballscratchsimulator.wiki/" rel="noopener noreferrer"&gt;ballscratchsimulator.wiki&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I would be interested in feedback on the source-constrained editorial model, the Astro implementation, or the decision to keep the initial sitemap to one page.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>astro</category>
      <category>cloudflarechallenge</category>
    </item>
    <item>
      <title>How I Built a Source-Tracked Roblox Wiki with Astro and No Database</title>
      <dc:creator>visiohex</dc:creator>
      <pubDate>Tue, 21 Jul 2026 13:16:27 +0000</pubDate>
      <link>https://dev.to/jy_wang_2c4d36f4d66adea80/how-i-built-a-source-tracked-roblox-wiki-with-astro-and-no-database-1610</link>
      <guid>https://dev.to/jy_wang_2c4d36f4d66adea80/how-i-built-a-source-tracked-roblox-wiki-with-astro-and-no-database-1610</guid>
      <description>&lt;p&gt;I built &lt;a href="https://anime-expeditions-wiki.org/" rel="noopener noreferrer"&gt;Anime Expeditions Wiki&lt;/a&gt; as an independent guide for the Roblox experience Anime Expeditions.&lt;/p&gt;

&lt;p&gt;The site helps players find current codes, compare units, plan early progression, and verify that they are joining the correct Roblox experience. It now contains 47 indexable routes across seven languages.&lt;/p&gt;

&lt;p&gt;The technical stack is small:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Astro and TypeScript&lt;/li&gt;
&lt;li&gt;Static deployment on Cloudflare Pages&lt;/li&gt;
&lt;li&gt;No database&lt;/li&gt;
&lt;li&gt;No CMS&lt;/li&gt;
&lt;li&gt;No server functions&lt;/li&gt;
&lt;li&gt;No public API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The harder part was handling information that changes faster than the pages around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The maintenance problem: uncertainty
&lt;/h2&gt;

&lt;p&gt;Game guides become outdated fast. Codes expire, updates change upgrade requirements, and community tier lists often disagree.&lt;/p&gt;

&lt;p&gt;A page can show a recent publication date while its tables still describe an older game version. Copying the newest value into a database would not solve that problem. I needed to record where each claim came from and the conditions under which it was useful.&lt;/p&gt;

&lt;p&gt;I treated changing gameplay claims as dated evidence records.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;EvidenceRecord&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;sourceIds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;evidenceLevel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EvidenceLevel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;verifiedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;confirmed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reported&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;disputed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unknown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;applicableVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each record answers five questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Which sources support this claim?&lt;/li&gt;
&lt;li&gt;What kind of source supplied it?&lt;/li&gt;
&lt;li&gt;When did someone check it?&lt;/li&gt;
&lt;li&gt;Do current sources agree?&lt;/li&gt;
&lt;li&gt;Which game version does it describe?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The public page displays this information beside the value. Readers do not have to search a general sources page to learn whether a code or recipe comes from an official record, a community guide, or conflicting reports.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping disagreements visible
&lt;/h2&gt;

&lt;p&gt;The code list shows why the status field matters.&lt;/p&gt;

&lt;p&gt;At the July 21 snapshot, the wiki tracked 15 codes. Eight had matching reports that they were active. Seven had conflicting reports.&lt;/p&gt;

&lt;p&gt;I kept the disputed codes visible and added notes describing which sources disagreed. Choosing one answer would hide useful information. Combining several reports into a new answer would create a claim that no source published.&lt;/p&gt;

&lt;p&gt;Tier lists have the same problem. The wiki compares three public rankings side by side instead of calculating a new score.&lt;/p&gt;

&lt;p&gt;If one publisher does not list a unit, the table displays “Not listed.” It does not convert the missing entry into the lowest grade.&lt;/p&gt;

&lt;p&gt;The data model allows disagreement to remain a valid state. Editors can update a record later when stronger evidence appears.&lt;/p&gt;

&lt;h2&gt;
  
  
  One dataset for 47 routes
&lt;/h2&gt;

&lt;p&gt;Gameplay records live in TypeScript data modules. Astro components decide how to present them.&lt;/p&gt;

&lt;p&gt;The current dataset includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;15 tracked codes&lt;/li&gt;
&lt;li&gt;33 units&lt;/li&gt;
&lt;li&gt;17 traits&lt;/li&gt;
&lt;li&gt;Evolution recipes&lt;/li&gt;
&lt;li&gt;Five story maps&lt;/li&gt;
&lt;li&gt;Equipment and resource records&lt;/li&gt;
&lt;li&gt;35 source entries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The static build produces 47 indexable routes. English has 17 pages. Spanish, Brazilian Portuguese, German, French, Japanese, and Korean each have five core pages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Homepage&lt;/li&gt;
&lt;li&gt;Codes&lt;/li&gt;
&lt;li&gt;Beginner guide&lt;/li&gt;
&lt;li&gt;Tier list&lt;/li&gt;
&lt;li&gt;Official links&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All seven languages share the same gameplay records. Only interface text and explanatory copy vary by locale.&lt;/p&gt;

&lt;p&gt;When a code changes status, I update one record instead of editing seven tables. This also prevents a translated page from keeping an older reward after the English page changes.&lt;/p&gt;

&lt;p&gt;Astro generates canonical URLs, &lt;code&gt;hreflang&lt;/code&gt; links, metadata, and the sitemap from the route definitions. A missing locale route fails during verification instead of producing an orphaned page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verifying the correct Roblox experience
&lt;/h2&gt;

&lt;p&gt;Roblox uses several identifiers for one experience. Anime Expeditions has a Place ID, Universe ID, and creator Group ID.&lt;/p&gt;

&lt;p&gt;A title search cannot prove that a result belongs to the expected creator, especially when several experiences use similar names.&lt;/p&gt;

&lt;p&gt;The project fixes these identifiers in its configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Place ID: &lt;code&gt;84515722934860&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Universe ID: &lt;code&gt;7613921865&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Creator: &lt;code&gt;Expeditions Entertainment&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Group ID: &lt;code&gt;35929511&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A synchronization script checks the Place-to-Universe relationship through Roblox. It finds the matching search record, verifies the creator, and confirms that approved promotional images belong to the expected universe.&lt;/p&gt;

&lt;p&gt;The script stops without writing when an identity field does not match.&lt;/p&gt;

&lt;p&gt;A changed title may be a legitimate update. A different creator or Universe ID means the automation may have found the wrong experience.&lt;/p&gt;

&lt;p&gt;The site also keeps a small whitelist of official Roblox and YouTube destinations. A data update cannot introduce an unreviewed game, group, or media URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automating stable facts
&lt;/h2&gt;

&lt;p&gt;A daily GitHub Actions workflow checks official Roblox metadata.&lt;/p&gt;

&lt;p&gt;It compares stable fields with a stored snapshot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name&lt;/li&gt;
&lt;li&gt;Description&lt;/li&gt;
&lt;li&gt;Canonical path&lt;/li&gt;
&lt;li&gt;Content maturity&lt;/li&gt;
&lt;li&gt;Age recommendation&lt;/li&gt;
&lt;li&gt;Creator&lt;/li&gt;
&lt;li&gt;Place and Universe IDs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The workflow ignores player counts, vote totals, promotion state, and thumbnail URLs. Those values change often and do not justify a content update.&lt;/p&gt;

&lt;p&gt;The identity check passes its verified search result to the snapshot step. Reusing that result avoids calling Roblox’s rate-limited search endpoint twice during one workflow run.&lt;/p&gt;

&lt;p&gt;When stable official metadata changes, the workflow can update the snapshot and deploy the static site.&lt;/p&gt;

&lt;p&gt;It does not rewrite tier lists, code statuses, evolution recipes, or gameplay advice.&lt;/p&gt;

&lt;p&gt;Community information still needs review. Two guides may disagree because one tested an older server version, one misunderstood a requirement, or both copied the same incorrect source. A script cannot settle that question from the published values alone.&lt;/p&gt;

&lt;p&gt;Automation handles identity and stable platform metadata. A human reviews gameplay claims.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the site stays static
&lt;/h2&gt;

&lt;p&gt;Astro is the project’s only application dependency. Cloudflare Pages serves the generated files.&lt;/p&gt;

&lt;p&gt;A database would add another moving part without improving the evidence behind the content. The dataset remains small enough to review in Git, and the site rebuilds when a record changes.&lt;/p&gt;

&lt;p&gt;Static output also makes verification easier. The build process can inspect the HTML that will reach production.&lt;/p&gt;

&lt;p&gt;The validator checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Page titles and descriptions&lt;/li&gt;
&lt;li&gt;Canonical URLs&lt;/li&gt;
&lt;li&gt;Alternate language links&lt;/li&gt;
&lt;li&gt;Internal links&lt;/li&gt;
&lt;li&gt;Source references&lt;/li&gt;
&lt;li&gt;Structured data&lt;/li&gt;
&lt;li&gt;Local media&lt;/li&gt;
&lt;li&gt;Security requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Browser JavaScript handles a short list of enhancements, including table search, filters, code copying, mobile navigation, and analytics consent.&lt;/p&gt;

&lt;p&gt;Readers can still access the main content when those enhancements fail.&lt;/p&gt;

&lt;p&gt;The official trailer uses a click-to-load YouTube embed. The browser does not contact YouTube until the visitor starts the video. Google Analytics also stays unloaded until a visitor opts in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing content relationships
&lt;/h2&gt;

&lt;p&gt;The test suite checks content assumptions that TypeScript cannot protect on its own.&lt;/p&gt;

&lt;p&gt;It verifies that every source ID is unique and that every citation points to an existing source. It also checks that tier comparisons cover the known unit roster without inventing grades for omitted units.&lt;/p&gt;

&lt;p&gt;Other tests protect the update and publishing workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The official Roblox identity must remain fixed.&lt;/li&gt;
&lt;li&gt;The update check must reuse verified records.&lt;/li&gt;
&lt;li&gt;Roblox requests must back off after rate limits.&lt;/li&gt;
&lt;li&gt;Failed identity checks must stop without writing.&lt;/li&gt;
&lt;li&gt;Media must stay local and attributed.&lt;/li&gt;
&lt;li&gt;YouTube and analytics must remain inactive until requested.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The current suite contains 13 tests. The Astro build generates 48 pages, including the 404 page, and the validator checks all 47 indexable routes.&lt;/p&gt;

&lt;p&gt;A valid TypeScript object can still cite a missing source, publish the wrong canonical domain, or send a translated page to an English URL. These checks catch those mistakes before deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits of this approach
&lt;/h2&gt;

&lt;p&gt;The static workflow makes editorial changes slower than editing a database through an admin panel. Each change passes through the repository, tests, and deployment.&lt;/p&gt;

&lt;p&gt;That cost fits the current project. A review step helps when one incorrect value could cause players to waste limited resources.&lt;/p&gt;

&lt;p&gt;Only five core pages have complete translations. Translating all 17 English pages would add substantial review work. Shared gameplay data prevents values from drifting, but translated explanations still need language-specific review.&lt;/p&gt;

&lt;p&gt;The evidence model also stays small. It records the source, status, date, and applicable version without calculating a confidence percentage.&lt;/p&gt;

&lt;p&gt;A confidence score would suggest more precision than the source material supports. Showing that two current guides disagree gives readers clearer information than assigning the claim a score of 62 percent.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;Dates and versions belong beside changing claims. A site-wide “last updated” date says little about an individual code or recipe.&lt;/p&gt;

&lt;p&gt;Source disagreement deserves its own state. Treating conflict as an error encourages maintainers to choose an answer before they have enough evidence.&lt;/p&gt;

&lt;p&gt;Automation works best on facts with stable identifiers and clear failure conditions. Editorial judgment needs a review boundary.&lt;/p&gt;

&lt;p&gt;The project remains a static Astro site because its current scale does not need more infrastructure. The useful part of the system is deciding what the build should refuse to publish.&lt;/p&gt;

&lt;p&gt;You can explore the finished project at &lt;a href="https://anime-expeditions-wiki.org/" rel="noopener noreferrer"&gt;Anime Expeditions Wiki&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Anime Expeditions Wiki is an independent fan project and is not affiliated with Roblox, Expeditions Entertainment, or any anime rights holder.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>astro</category>
      <category>typescript</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Installing Verity Mod Safely: Java vs Bedrock Setup Guide</title>
      <dc:creator>visiohex</dc:creator>
      <pubDate>Tue, 21 Jul 2026 13:14:16 +0000</pubDate>
      <link>https://dev.to/jy_wang_2c4d36f4d66adea80/installing-verity-mod-safely-java-vs-bedrock-setup-guide-482c</link>
      <guid>https://dev.to/jy_wang_2c4d36f4d66adea80/installing-verity-mod-safely-java-vs-bedrock-setup-guide-482c</guid>
      <description>&lt;p&gt;Most Verity Mod installation problems begin before Minecraft even launches.&lt;/p&gt;

&lt;p&gt;A player searches for “Verity Mod,” opens the first familiar-looking result, and installs it without checking the Minecraft edition, author, Project ID, loader, or game version.&lt;/p&gt;

&lt;p&gt;That is risky because &lt;strong&gt;Verity is not one interchangeable download&lt;/strong&gt;. There is a Java project, multiple Bedrock adaptations, modpacks, and unrelated projects using similar names.&lt;/p&gt;

&lt;p&gt;This guide provides a practical way to identify the correct project and troubleshoot it without relying on unofficial file mirrors.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Project information in this article was last checked on July 21, 2026.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What is Verity Mod?
&lt;/h2&gt;

&lt;p&gt;Verity is a Minecraft horror-companion concept associated with ThatMob’s video series.&lt;/p&gt;

&lt;p&gt;Playable adaptations introduce a yellow helper character that can answer questions, interact with the player, and eventually produce unsettling behavior or horror events.&lt;/p&gt;

&lt;p&gt;The important detail is that the Java and Bedrock versions are separate implementations.&lt;/p&gt;

&lt;p&gt;They do not share the same:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;File format&lt;/li&gt;
&lt;li&gt;Mod loader&lt;/li&gt;
&lt;li&gt;Dependencies&lt;/li&gt;
&lt;li&gt;AI configuration&lt;/li&gt;
&lt;li&gt;World settings&lt;/li&gt;
&lt;li&gt;Connection method&lt;/li&gt;
&lt;li&gt;Troubleshooting steps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A Java tutorial cannot safely be applied to a Bedrock add-on simply because both projects use the name Verity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Identify the project before installing it
&lt;/h2&gt;

&lt;p&gt;A project title is not a reliable identifier. Names and readable URL slugs can be copied, reused, or changed.&lt;/p&gt;

&lt;p&gt;Before installing anything, record these five values:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Minecraft edition&lt;/li&gt;
&lt;li&gt;Project author&lt;/li&gt;
&lt;li&gt;Numeric Project ID&lt;/li&gt;
&lt;li&gt;Exact Minecraft version&lt;/li&gt;
&lt;li&gt;Loader or import method&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These values work like a package identity. If one of them differs from the tutorial you are following, assume it is a different project until proven otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Projects checked for this guide
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Edition&lt;/th&gt;
&lt;th&gt;Project&lt;/th&gt;
&lt;th&gt;Author&lt;/th&gt;
&lt;th&gt;Project ID&lt;/th&gt;
&lt;th&gt;Checked release&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Java&lt;/td&gt;
&lt;td&gt;Verity JE&lt;/td&gt;
&lt;td&gt;VarmiteYT&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1591438&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;5.7.3&lt;/code&gt; for Minecraft &lt;code&gt;1.20.1&lt;/code&gt; with Forge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bedrock&lt;/td&gt;
&lt;td&gt;Verity BE&lt;/td&gt;
&lt;td&gt;Undertaletalelover&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1574632&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;1.0.8&lt;/code&gt; for Bedrock &lt;code&gt;26.30&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bedrock&lt;/td&gt;
&lt;td&gt;Verity - Bedrock Edition&lt;/td&gt;
&lt;td&gt;PnTMC&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1575941&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;2.1.0&lt;/code&gt; for Bedrock &lt;code&gt;26.30&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Always recheck the project page before installing. Releases and supported Minecraft versions can change after this article is published.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing Java or Bedrock
&lt;/h2&gt;

&lt;p&gt;Choose &lt;strong&gt;Java Edition&lt;/strong&gt; if you play Minecraft Java on a desktop computer and can create a Forge or NeoForge profile.&lt;/p&gt;

&lt;p&gt;Choose &lt;strong&gt;Bedrock Edition&lt;/strong&gt; if you play the current Minecraft release on Android, iOS, or a compatible Windows Bedrock installation.&lt;/p&gt;

&lt;p&gt;“Minecraft PE” is now commonly used as an informal name for Minecraft Bedrock on mobile. A Java mod file cannot be imported into Minecraft Bedrock or PE.&lt;/p&gt;

&lt;p&gt;A more detailed edition comparison is available here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://veritymod.org/java-vs-bedrock/" rel="noopener noreferrer"&gt;Verity Mod: Java vs Bedrock&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing Verity JE
&lt;/h2&gt;

&lt;p&gt;The checked Java project is &lt;strong&gt;Verity JE by VarmiteYT&lt;/strong&gt;, Project ID &lt;code&gt;1591438&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Its current main release is Verity JE &lt;code&gt;5.7.3&lt;/code&gt; for Minecraft &lt;code&gt;1.20.1&lt;/code&gt; with Forge.&lt;/p&gt;

&lt;p&gt;The project also lists a Minecraft &lt;code&gt;1.21.1&lt;/code&gt; NeoForge branch, but its project description warns that this branch is deprecated and has many bugs. A higher Minecraft version number does not automatically make it the better choice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recommended Java setup
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Create a separate Minecraft &lt;code&gt;1.20.1&lt;/code&gt; profile.&lt;/li&gt;
&lt;li&gt;Install the correct Forge version for that profile.&lt;/li&gt;
&lt;li&gt;Launch Minecraft once before adding any mods.&lt;/li&gt;
&lt;li&gt;Close Minecraft and add Verity JE.&lt;/li&gt;
&lt;li&gt;Add only the dependencies listed for the selected release.&lt;/li&gt;
&lt;li&gt;Launch the small test profile before combining Verity with a larger modpack.&lt;/li&gt;
&lt;li&gt;Configure one supported AI option.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The project supports an online Groq configuration and a local Ollama option.&lt;/p&gt;

&lt;p&gt;Keep API keys private. Never place a key in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Public screenshots&lt;/li&gt;
&lt;li&gt;GitHub repositories&lt;/li&gt;
&lt;li&gt;Blog comments&lt;/li&gt;
&lt;li&gt;Discord messages&lt;/li&gt;
&lt;li&gt;Shared configuration files&lt;/li&gt;
&lt;li&gt;Support requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If Minecraft fails before reaching the main menu, investigate the Minecraft version, Forge version, Java runtime, and dependencies before debugging the AI configuration.&lt;/p&gt;

&lt;p&gt;Official Java project page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.curseforge.com/minecraft/mc-mods/verity-je" rel="noopener noreferrer"&gt;Verity JE on CurseForge&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing Verity on Bedrock
&lt;/h2&gt;

&lt;p&gt;Bedrock users must make an additional choice because there is more than one maintained adaptation.&lt;/p&gt;

&lt;p&gt;The two projects covered here are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Verity BE by Undertaletalelover&lt;/strong&gt;, Project ID &lt;code&gt;1574632&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verity - Bedrock Edition by PnTMC&lt;/strong&gt;, Project ID &lt;code&gt;1575941&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They are separate projects with different releases, gameplay behavior, and support instructions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recommended Bedrock setup
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Choose one project by author and Project ID.&lt;/li&gt;
&lt;li&gt;Confirm that its release matches your exact Bedrock build.&lt;/li&gt;
&lt;li&gt;Import the add-on using Minecraft’s normal import process.&lt;/li&gt;
&lt;li&gt;Create a new test world.&lt;/li&gt;
&lt;li&gt;Activate the required behavior and resource packs.&lt;/li&gt;
&lt;li&gt;Enable the experimental settings required by that project.&lt;/li&gt;
&lt;li&gt;Follow the connection and interaction instructions from the same project page.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Do not copy a connection command from one Bedrock project into the other. Do not assume that a tutorial for Project ID &lt;code&gt;1574632&lt;/code&gt; also applies to Project ID &lt;code&gt;1575941&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Official Bedrock project pages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.curseforge.com/minecraft-bedrock/addons/verity-be" rel="noopener noreferrer"&gt;Verity BE on CurseForge&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.curseforge.com/minecraft-bedrock/addons/verity-bedrock-edition" rel="noopener noreferrer"&gt;Verity - Bedrock Edition on CurseForge&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A simple troubleshooting decision tree
&lt;/h2&gt;

&lt;p&gt;When Verity does not work, identify the first stage that fails.&lt;/p&gt;

&lt;h3&gt;
  
  
  Minecraft does not start
&lt;/h3&gt;

&lt;p&gt;Check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Minecraft version&lt;/li&gt;
&lt;li&gt;Forge or NeoForge version&lt;/li&gt;
&lt;li&gt;Java runtime&lt;/li&gt;
&lt;li&gt;Required dependencies&lt;/li&gt;
&lt;li&gt;Conflicting mods&lt;/li&gt;
&lt;li&gt;The profile’s actual mod directory&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Test with only Verity and its required dependencies before adding other mods.&lt;/p&gt;

&lt;h3&gt;
  
  
  Minecraft starts, but Verity does not appear
&lt;/h3&gt;

&lt;p&gt;For Java, confirm that the mod is loaded in the correct profile.&lt;/p&gt;

&lt;p&gt;For Bedrock, check that the behavior and resource packs are active in that world. Also confirm that the add-on supports the installed Bedrock version.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verity appears but does not respond
&lt;/h3&gt;

&lt;p&gt;For Java, verify the Groq or Ollama configuration.&lt;/p&gt;

&lt;p&gt;For Bedrock, follow the connection instructions for the exact Project ID you installed. Check required experimental options and current service notices.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verity only displays dots
&lt;/h3&gt;

&lt;p&gt;This may indicate an incomplete connection step or a temporary project service problem.&lt;/p&gt;

&lt;p&gt;Repeatedly reinstalling the same file usually does not solve a provider or backend issue. Check the project page and its official support channel before changing unrelated settings.&lt;/p&gt;

&lt;p&gt;The complete troubleshooting checklist is available here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://veritymod.org/not-working/" rel="noopener noreferrer"&gt;Fix Verity Mod Not Responding&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Avoid unsafe and incorrect downloads
&lt;/h2&gt;

&lt;p&gt;Do not select a Verity file based only on its title, thumbnail, or download count.&lt;/p&gt;

&lt;p&gt;Before installing, verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Author&lt;/li&gt;
&lt;li&gt;Project ID&lt;/li&gt;
&lt;li&gt;Minecraft edition&lt;/li&gt;
&lt;li&gt;Exact game version&lt;/li&gt;
&lt;li&gt;Loader&lt;/li&gt;
&lt;li&gt;Release date&lt;/li&gt;
&lt;li&gt;Changelog&lt;/li&gt;
&lt;li&gt;Required dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avoid downloading mod files from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shared drives&lt;/li&gt;
&lt;li&gt;URL shorteners&lt;/li&gt;
&lt;li&gt;Chat attachments&lt;/li&gt;
&lt;li&gt;Repost websites&lt;/li&gt;
&lt;li&gt;Unknown mirrors&lt;/li&gt;
&lt;li&gt;Comments claiming to preserve a removed version&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A removed project is not automatically malicious. However, an untraceable copy has no reliable source, update history, or trustworthy relationship to the original author.&lt;/p&gt;

&lt;p&gt;The following guide links to project pages instead of hosting mod files:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://veritymod.org/download/" rel="noopener noreferrer"&gt;Verity Mod Download Sources&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Back up important worlds
&lt;/h2&gt;

&lt;p&gt;Horror mods and experimental add-ons may intentionally alter normal gameplay.&lt;/p&gt;

&lt;p&gt;Before testing Verity:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use a separate launcher profile for Java.&lt;/li&gt;
&lt;li&gt;Create a new test world for Bedrock.&lt;/li&gt;
&lt;li&gt;Back up any world you care about.&lt;/li&gt;
&lt;li&gt;Do not test experimental releases on your only copy of a world.&lt;/li&gt;
&lt;li&gt;Add other mods gradually after the base setup works.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A clean test environment is not only safer. It also makes troubleshooting much faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final checklist
&lt;/h2&gt;

&lt;p&gt;Before launching Verity, confirm that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You selected Java or Bedrock correctly.&lt;/li&gt;
&lt;li&gt;The author matches the project you intended to install.&lt;/li&gt;
&lt;li&gt;The numeric Project ID matches your tutorial.&lt;/li&gt;
&lt;li&gt;The exact Minecraft version is supported.&lt;/li&gt;
&lt;li&gt;The loader or add-on import method is correct.&lt;/li&gt;
&lt;li&gt;Required dependencies and world settings are enabled.&lt;/li&gt;
&lt;li&gt;API keys remain private.&lt;/li&gt;
&lt;li&gt;Important worlds are backed up.&lt;/li&gt;
&lt;li&gt;The file came from the project page rather than a mirror.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most Verity installation failures can be prevented by checking identity and compatibility before downloading anything.&lt;/p&gt;

&lt;p&gt;For maintained comparisons, setup instructions, and safety notes, visit:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://veritymod.org/" rel="noopener noreferrer"&gt;Verity Mod Guide&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;This is an independent, unofficial guide. It is not affiliated with Mojang, Microsoft, CurseForge, ThatMob, or the developers of the referenced Verity projects.&lt;/p&gt;

</description>
      <category>minecraft</category>
      <category>gaming</category>
      <category>tutorial</category>
      <category>security</category>
    </item>
    <item>
      <title>I built Img2Gen, a GPT Image 2 studio on Cloudflare Workers</title>
      <dc:creator>visiohex</dc:creator>
      <pubDate>Fri, 05 Jun 2026 04:14:57 +0000</pubDate>
      <link>https://dev.to/jy_wang_2c4d36f4d66adea80/i-built-img2gen-a-gpt-image-2-studio-on-cloudflare-workers-1m0e</link>
      <guid>https://dev.to/jy_wang_2c4d36f4d66adea80/i-built-img2gen-a-gpt-image-2-studio-on-cloudflare-workers-1m0e</guid>
      <description>&lt;p&gt;I recently built &lt;a href="https://img2gen.com" rel="noopener noreferrer"&gt;Img2Gen&lt;/a&gt;, a focused web studio for GPT Image 2 generation.&lt;/p&gt;

&lt;p&gt;The idea was simple: I wanted something more practical than a single prompt box. For real creative work, I often need to try multiple aspect ratios, upload reference images, generate several variants, download the results, and keep a history of what worked.&lt;/p&gt;

&lt;p&gt;So Img2Gen supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Text-to-image generation&lt;/li&gt;
&lt;li&gt;Reference-image edits with up to 5 uploaded images&lt;/li&gt;
&lt;li&gt;1, 2, or 4 outputs per request&lt;/li&gt;
&lt;li&gt;Multiple aspect ratios&lt;/li&gt;
&lt;li&gt;Low, medium, and high quality modes&lt;/li&gt;
&lt;li&gt;Daily free generations&lt;/li&gt;
&lt;li&gt;Credit-based paid usage&lt;/li&gt;
&lt;li&gt;Generation history and downloads&lt;/li&gt;
&lt;li&gt;Automatic refunds when a generation fails&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;p&gt;The app is built with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TanStack Start&lt;/li&gt;
&lt;li&gt;React 19&lt;/li&gt;
&lt;li&gt;Cloudflare Workers&lt;/li&gt;
&lt;li&gt;Cloudflare D1 with Drizzle ORM&lt;/li&gt;
&lt;li&gt;Cloudflare R2 for generated image storage&lt;/li&gt;
&lt;li&gt;Better Auth&lt;/li&gt;
&lt;li&gt;Stripe / Creem for payments&lt;/li&gt;
&lt;li&gt;A server-side GPT Image 2 generation provider&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanted the whole thing to run close to the edge, avoid a traditional server, and keep the architecture small enough that I could iterate quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Cloudflare Workers?
&lt;/h2&gt;

&lt;p&gt;Image generation itself is slow compared with a normal web request, so the main app has to handle queued jobs, polling, persistence, and failures cleanly.&lt;/p&gt;

&lt;p&gt;The flow looks roughly like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User submits a prompt and optional reference images&lt;/li&gt;
&lt;li&gt;The server validates the request and checks moderation&lt;/li&gt;
&lt;li&gt;Free daily usage or credits are reserved&lt;/li&gt;
&lt;li&gt;A generation job is submitted to the provider&lt;/li&gt;
&lt;li&gt;The client polls for status&lt;/li&gt;
&lt;li&gt;Successful images are downloaded, moderated, and persisted to R2&lt;/li&gt;
&lt;li&gt;The generation record is saved in D1&lt;/li&gt;
&lt;li&gt;Credits are refunded if the job fails&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The hardest part was not the UI. It was making the generation workflow feel reliable when the underlying job can take a while or fail halfway through.&lt;/p&gt;

&lt;h2&gt;
  
  
  Credit Handling
&lt;/h2&gt;

&lt;p&gt;I ended up using a credit model because image quality and output count change the cost.&lt;/p&gt;

&lt;p&gt;Currently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Low quality: 1 credit per image&lt;/li&gt;
&lt;li&gt;Medium quality: 4 credits per image&lt;/li&gt;
&lt;li&gt;High quality: 16 credits per image&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Free users get daily generations, and paid users can use monthly credits or credit packs.&lt;/p&gt;

&lt;p&gt;One small detail I cared about: failed generations should not silently burn credits. If a job fails after credits are reserved, the system refunds them automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reference Images
&lt;/h2&gt;

&lt;p&gt;Reference-image editing was one of the main reasons I built this.&lt;/p&gt;

&lt;p&gt;Img2Gen accepts PNG, JPG, and WebP references, up to 5 images per request. This makes it useful for product shots, ad creatives, portraits, style exploration, and iterative edits where you want the output to stay anchored to existing visuals.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;A few takeaways from building it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI image apps need good job recovery, not just good prompts.&lt;/li&gt;
&lt;li&gt;Credit systems need to be boring and predictable.&lt;/li&gt;
&lt;li&gt;Storing generated outputs matters because provider URLs are not always a long-term product experience.&lt;/li&gt;
&lt;li&gt;A focused workflow can be more useful than exposing every possible model setting.&lt;/li&gt;
&lt;li&gt;Edge/serverless architectures work well here, but long-running generation flows need careful state design.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;You can try it here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://img2gen.com" rel="noopener noreferrer"&gt;https://img2gen.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I would love feedback from other builders, especially on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Whether the generation flow feels clear&lt;/li&gt;
&lt;li&gt;Whether the credit model is understandable&lt;/li&gt;
&lt;li&gt;What reference-image workflows you would expect from a tool like this&lt;/li&gt;
&lt;li&gt;Any architecture suggestions for long-running AI generation jobs on serverless platforms&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>cloudflarechallenge</category>
      <category>react</category>
    </item>
    <item>
      <title>I built a browser-only tool to remove visible Gemini watermarks</title>
      <dc:creator>visiohex</dc:creator>
      <pubDate>Sat, 18 Apr 2026 14:13:22 +0000</pubDate>
      <link>https://dev.to/jy_wang_2c4d36f4d66adea80/i-built-a-browser-only-tool-to-remove-visible-gemini-watermarks-45ke</link>
      <guid>https://dev.to/jy_wang_2c4d36f4d66adea80/i-built-a-browser-only-tool-to-remove-visible-gemini-watermarks-45ke</guid>
      <description>&lt;p&gt;I built a small tool called &lt;a href="https://watermarkzero.org/" rel="noopener noreferrer"&gt;WatermarkZero&lt;/a&gt; to handle one specific case: the visible Gemini watermark that shows up in the bottom-right corner of supported Gemini-generated images.&lt;/p&gt;

&lt;p&gt;Site: &lt;a href="https://watermarkzero.org/" rel="noopener noreferrer"&gt;https://watermarkzero.org/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/liuyan-wjy/watermarkzero" rel="noopener noreferrer"&gt;https://github.com/liuyan-wjy/watermarkzero&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most tools I found went in one of three directions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;they cropped the corner&lt;/li&gt;
&lt;li&gt;they pushed the file through a remote server&lt;/li&gt;
&lt;li&gt;they were vague about what they actually supported&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanted something narrower and more explicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  What WatermarkZero does
&lt;/h2&gt;

&lt;p&gt;WatermarkZero runs in the browser and focuses on the visible Gemini mark only. You upload a JPG, PNG, or WebP image, preview the result, and download the cleaned version without sending the file to a backend.&lt;/p&gt;

&lt;p&gt;That constraint shaped the whole project. I was not trying to build a general watermark remover. I wanted a tool with a clear boundary and a workflow that people could understand in a few seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why local processing mattered
&lt;/h2&gt;

&lt;p&gt;Image tools often ask you to upload files before they explain anything. That works for some use cases, but it feels wrong for screenshots, drafts, or personal images that you do not want sitting on a third-party server.&lt;/p&gt;

&lt;p&gt;So I kept the core flow local:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;image selection happens in the browser&lt;/li&gt;
&lt;li&gt;processing happens in the browser&lt;/li&gt;
&lt;li&gt;downloading happens in the browser&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That also forced me to keep the UX honest. If the restoration is weak on a certain image, you see it right away.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scope matters more than hype
&lt;/h2&gt;

&lt;p&gt;One thing I tried to avoid was pretending this works on everything.&lt;/p&gt;

&lt;p&gt;WatermarkZero is built for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the visible Gemini watermark&lt;/li&gt;
&lt;li&gt;supported Gemini-generated images&lt;/li&gt;
&lt;li&gt;cases where you want to keep the full frame instead of cropping the corner&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is not built for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;hidden watermarking systems like SynthID&lt;/li&gt;
&lt;li&gt;every kind of logo or watermark on the internet&lt;/li&gt;
&lt;li&gt;guaranteed success on every textured corner or gradient-heavy image&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That narrower scope made the product better. It also made the copy easier to write, because I did not have to blur the edges.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I shipped
&lt;/h2&gt;

&lt;p&gt;The current version includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;local browser processing&lt;/li&gt;
&lt;li&gt;before/after examples&lt;/li&gt;
&lt;li&gt;downloadable output&lt;/li&gt;
&lt;li&gt;support for JPG, PNG, and WebP&lt;/li&gt;
&lt;li&gt;a few support pages that explain how it works and where it fails&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also spent time on the boring parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;mobile layout&lt;/li&gt;
&lt;li&gt;simple analytics&lt;/li&gt;
&lt;li&gt;Cloudflare deployment&lt;/li&gt;
&lt;li&gt;clear FAQ and privacy pages&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The harder cases
&lt;/h2&gt;

&lt;p&gt;The hardest inputs are the ones you would expect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fabric folds&lt;/li&gt;
&lt;li&gt;leaves and plant textures&lt;/li&gt;
&lt;li&gt;soft gradients&lt;/li&gt;
&lt;li&gt;detailed corners where the visible mark overlaps real image detail&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those cases are useful because they tell you whether the result looks restored or just blurred over.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you want to try it
&lt;/h2&gt;

&lt;p&gt;The tool is here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://watermarkzero.org/" rel="noopener noreferrer"&gt;https://watermarkzero.org/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you test it, I would love to hear where it breaks down. Failure cases are more useful than generic praise on a tool this narrow.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>showdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I built a spoiler-safe NYT Connections helper with Astro and Cloudflare</title>
      <dc:creator>visiohex</dc:creator>
      <pubDate>Tue, 14 Apr 2026 09:14:09 +0000</pubDate>
      <link>https://dev.to/jy_wang_2c4d36f4d66adea80/i-built-a-spoiler-safe-nyt-connections-helper-with-astro-and-cloudflare-24pf</link>
      <guid>https://dev.to/jy_wang_2c4d36f4d66adea80/i-built-a-spoiler-safe-nyt-connections-helper-with-astro-and-cloudflare-24pf</guid>
      <description>&lt;p&gt;I recently built &lt;a href="https://connections-hint.today/" rel="noopener noreferrer"&gt;Connections Hint Today&lt;/a&gt;, a spoiler-safe helper for NYT Connections.&lt;/p&gt;

&lt;p&gt;The original idea was pretty simple: most Connections helper pages solve the wrong problem.&lt;/p&gt;

&lt;p&gt;They are useful if you want the answer right now, but a lot of them jump straight to spoilers. That works for panic-click traffic, but it does not feel great if you still want to solve the board yourself. I wanted something that behaved more like a coach than an answer dump.&lt;/p&gt;

&lt;p&gt;So I built a small site around that idea:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;light hints first&lt;/li&gt;
&lt;li&gt;stronger hints only if needed&lt;/li&gt;
&lt;li&gt;archive pages for review&lt;/li&gt;
&lt;li&gt;Sports Edition support&lt;/li&gt;
&lt;li&gt;short explanation and trap notes for each group&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The site is live here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://connections-hint.today/" rel="noopener noreferrer"&gt;https://connections-hint.today/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the repo is here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/liuyan-wjy/connections-coach" rel="noopener noreferrer"&gt;https://github.com/liuyan-wjy/connections-coach&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The product idea
&lt;/h2&gt;

&lt;p&gt;The most important product decision was to treat “help” and “spoiler” as two different things.&lt;/p&gt;

&lt;p&gt;If you play Connections regularly, you usually do not want a giant answer page. You want a nudge. Maybe you are stuck between two groupings. Maybe you know one category is structural but cannot see it yet. Maybe the purple group is clearly some kind of phrase pattern, but you are not sure which one.&lt;/p&gt;

&lt;p&gt;That is a very different user need from “just show me the answer”.&lt;/p&gt;

&lt;p&gt;So the site uses a layered model:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Light hint&lt;/li&gt;
&lt;li&gt;Medium hint&lt;/li&gt;
&lt;li&gt;Strong hint&lt;/li&gt;
&lt;li&gt;Reveal answer and explanation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That sounds small, but it changes the whole tone of the product. Once I committed to that, the rest of the site became easier to shape: archive pages, strategy pages, and even the result analyzer all needed to support the same idea of helping without flattening the puzzle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Astro + Cloudflare
&lt;/h2&gt;

&lt;p&gt;This project is a pretty good fit for the Astro + Cloudflare stack.&lt;/p&gt;

&lt;p&gt;I wanted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;static-first pages for SEO&lt;/li&gt;
&lt;li&gt;fast page loads&lt;/li&gt;
&lt;li&gt;simple deployment&lt;/li&gt;
&lt;li&gt;low ops overhead&lt;/li&gt;
&lt;li&gt;enough server-side logic to ingest and publish daily puzzle data automatically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Astro gave me a clean content-heavy frontend without dragging in a lot of complexity. Cloudflare gave me a surprisingly compact deployment model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cloudflare Pages for the site&lt;/li&gt;
&lt;li&gt;Workers for the ingest pipeline&lt;/li&gt;
&lt;li&gt;D1 for puzzle storage&lt;/li&gt;
&lt;li&gt;Browser Rendering as a fallback for dynamic Sports Edition capture&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That combination let me keep the public site lightweight while still handling the scheduled automation behind the scenes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ingestion problem
&lt;/h2&gt;

&lt;p&gt;The most interesting part of the project turned out not to be the UI. It was the ingest pipeline.&lt;/p&gt;

&lt;p&gt;The main Connections board and the Sports Edition do not behave the same way. The main edition was fairly straightforward once I found the right live daily endpoint. Sports was messier.&lt;/p&gt;

&lt;p&gt;At first, I tried to capture Sports data through browser automation. That worked some of the time, but it was not stable enough for a daily publishing flow. The browser connection would occasionally time out, which is exactly the kind of failure you do not want in a project that is supposed to update on its own every day.&lt;/p&gt;

&lt;p&gt;The fix was to change the ingestion strategy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use a direct GraphQL request first&lt;/li&gt;
&lt;li&gt;keep browser capture only as fallback&lt;/li&gt;
&lt;li&gt;write both &lt;code&gt;main&lt;/code&gt; and &lt;code&gt;sports&lt;/code&gt; puzzles into D1&lt;/li&gt;
&lt;li&gt;mark them as published only when the payload is valid&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once that was in place, daily publishing became much more reliable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule-based copy generation
&lt;/h2&gt;

&lt;p&gt;Another interesting challenge was content generation.&lt;/p&gt;

&lt;p&gt;I did not want every puzzle page to say the same thing with a new date stamped on it. At the same time, I also did not want to rely on live AI generation for every daily update. That would increase cost, add latency, and make the output less predictable.&lt;/p&gt;

&lt;p&gt;So I built a rule-based copy layer instead.&lt;/p&gt;

&lt;p&gt;Right now the generation works more like a lightweight editorial engine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;detect whether a group is direct, structural, phrase-based, or sports-specific&lt;/li&gt;
&lt;li&gt;generate different hint and explanation styles based on that category shape&lt;/li&gt;
&lt;li&gt;vary summary copy by puzzle type&lt;/li&gt;
&lt;li&gt;keep the tone consistent across the site&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is not trying to imitate a human writer perfectly. It is trying to be useful, stable, and a little less repetitive than a pure template system.&lt;/p&gt;

&lt;p&gt;That tradeoff feels right for this stage of the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  SEO and information architecture
&lt;/h2&gt;

&lt;p&gt;Because this is a search-heavy product, structure mattered a lot from the start.&lt;/p&gt;

&lt;p&gt;I did not want the site to be just a single &lt;code&gt;/today&lt;/code&gt; page. That would make it too dependent on one expiring keyword pattern.&lt;/p&gt;

&lt;p&gt;Instead, I split it into a few clear surfaces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/today&lt;/code&gt; for fast daily usage&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/puzzle/{id}&lt;/code&gt; as the canonical puzzle pages&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/archive&lt;/code&gt; for review&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/sports&lt;/code&gt; for Sports Edition&lt;/li&gt;
&lt;li&gt;strategy pages for evergreen traffic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also made &lt;code&gt;/today&lt;/code&gt; &lt;code&gt;noindex,follow&lt;/code&gt; and pointed canonical to the actual puzzle detail page. That gave the site a cleaner search shape and reduced duplication risk.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;A few things stood out while building this:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The real product is not the answer page
&lt;/h3&gt;

&lt;p&gt;The answer page gets the initial traffic, but the real product is the habit loop around it.&lt;/p&gt;

&lt;p&gt;If someone comes back for archive review, strategy pages, or Sports Edition, that is much more valuable than a one-time click for today’s board.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Small automation details matter more than flashy features
&lt;/h3&gt;

&lt;p&gt;The site became meaningfully better once the daily ingest was reliable. That mattered more than adding another feature.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Distribution rules matter
&lt;/h3&gt;

&lt;p&gt;Communities that seem like an obvious fit are not always friendly to direct links. Some puzzle communities treat any link to a current-board helper as a spoiler, even if the page is intentionally spoiler-safe.&lt;/p&gt;

&lt;p&gt;That means the product and the distribution strategy have to be designed together.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. AI is optional
&lt;/h3&gt;

&lt;p&gt;A lot of projects like this immediately jump to “just use AI for the content”. I think that is often the wrong first move.&lt;/p&gt;

&lt;p&gt;For this project, deterministic rules got me much farther than I expected. They are cheaper, easier to test, and easier to reason about when something goes wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is next
&lt;/h2&gt;

&lt;p&gt;The next things I want to improve are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;better puzzle-specific commentary&lt;/li&gt;
&lt;li&gt;more evergreen strategy content&lt;/li&gt;
&lt;li&gt;stronger archive depth&lt;/li&gt;
&lt;li&gt;clearer post-game analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also want to keep refining the copy so it feels less like a generated system and more like a compact field guide for repeat players.&lt;/p&gt;

&lt;p&gt;If you build content-heavy side projects, I would be curious how you think about this tradeoff:&lt;/p&gt;

&lt;p&gt;When do you keep a site deterministic, and when do you add AI into the publishing path?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>astro</category>
      <category>cloudflarechallenge</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>What Is AutoResearch by Andrej Karpathy? A Clearer Guide for First-Time Readers</title>
      <dc:creator>visiohex</dc:creator>
      <pubDate>Wed, 25 Mar 2026 08:59:55 +0000</pubDate>
      <link>https://dev.to/jy_wang_2c4d36f4d66adea80/what-is-autoresearch-by-andrej-karpathy-a-clearer-guide-for-first-time-readers-23kf</link>
      <guid>https://dev.to/jy_wang_2c4d36f4d66adea80/what-is-autoresearch-by-andrej-karpathy-a-clearer-guide-for-first-time-readers-23kf</guid>
      <description>&lt;h1&gt;
  
  
  What Is AutoResearch by Andrej Karpathy? A Clearer Guide for First-Time Readers
&lt;/h1&gt;

&lt;p&gt;Karpathy's &lt;code&gt;autoresearch&lt;/code&gt; repository has been getting a lot of attention recently.&lt;/p&gt;

&lt;p&gt;A lot of people are searching for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;autoresearch github&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;karpathy autoresearch&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;autoresearch ai&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But if you're seeing the project for the first time, the raw GitHub repository can still feel a bit dense.&lt;/p&gt;

&lt;p&gt;So I built a small unofficial guide site to make the project easier to understand before diving into the source code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What AutoResearch Is
&lt;/h2&gt;

&lt;p&gt;At a high level, AutoResearch is an open-source project that lets AI agents run repeated ML training experiments inside a compact codebase.&lt;/p&gt;

&lt;p&gt;Instead of manually editing everything yourself, the idea is to let an agent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;modify training logic&lt;/li&gt;
&lt;li&gt;run short experiments&lt;/li&gt;
&lt;li&gt;compare results&lt;/li&gt;
&lt;li&gt;iterate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That makes it interesting not just as a repo, but as a preview of a different way to do ML experimentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why People Care About It
&lt;/h2&gt;

&lt;p&gt;A few things make the project stand out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it comes from Andrej Karpathy&lt;/li&gt;
&lt;li&gt;it is small enough to inspect&lt;/li&gt;
&lt;li&gt;it fits into the current wave of interest around coding agents&lt;/li&gt;
&lt;li&gt;it creates a more readable “research loop” than a giant ML stack&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For developers and AI builders, it is the kind of repo that becomes popular very quickly because it is both practical and conceptually interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Made a Separate Guide
&lt;/h2&gt;

&lt;p&gt;A GitHub README is great if you already know what you're looking for.&lt;/p&gt;

&lt;p&gt;But many searchers are not actually asking for source code first. They are asking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what this repo does&lt;/li&gt;
&lt;li&gt;where to start&lt;/li&gt;
&lt;li&gt;whether it can run on smaller hardware&lt;/li&gt;
&lt;li&gt;whether cloud GPUs make more sense&lt;/li&gt;
&lt;li&gt;how it differs from similarly named projects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is why I turned it into a small explainer site.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Published
&lt;/h2&gt;

&lt;p&gt;I broke the topic into a few pages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Homepage / overview: &lt;a href="https://openrepoguide.com" rel="noopener noreferrer"&gt;Open Repo Guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub explainer: &lt;a href="https://openrepoguide.com/autoresearch-github-guide/" rel="noopener noreferrer"&gt;AutoResearch GitHub Guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Step-by-step tutorial: &lt;a href="https://openrepoguide.com/autoresearch-tutorial/" rel="noopener noreferrer"&gt;AutoResearch Tutorial&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Hardware limitations: &lt;a href="https://openrepoguide.com/run-autoresearch-on-mac-or-small-gpus/" rel="noopener noreferrer"&gt;Run AutoResearch on Mac or Smaller GPUs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Neutral infra guide: &lt;a href="https://openrepoguide.com/best-gpu-cloud-for-autoresearch/" rel="noopener noreferrer"&gt;Best GPU Cloud Options for AutoResearch&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I’m Testing
&lt;/h2&gt;

&lt;p&gt;This is also a small experiment for me.&lt;/p&gt;

&lt;p&gt;I’m exploring whether a simple content site can grow by taking fast-rising GitHub repositories and turning them into clearer, more beginner-friendly explainer pages.&lt;/p&gt;

&lt;p&gt;Not official product sites.&lt;br&gt;
More like independent project guides for people discovering these repos through search.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feedback Welcome
&lt;/h2&gt;

&lt;p&gt;If you’ve looked at &lt;code&gt;autoresearch&lt;/code&gt;, I’d love to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what confused you most when you first saw it&lt;/li&gt;
&lt;li&gt;whether this kind of explainer page is actually useful&lt;/li&gt;
&lt;li&gt;which GitHub repos would be worth covering next&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>programming</category>
    </item>
    <item>
      <title>I Built a Free AI Instagram Bio Generator in 10 Days — Here's What I Learned</title>
      <dc:creator>visiohex</dc:creator>
      <pubDate>Wed, 18 Mar 2026 02:10:44 +0000</pubDate>
      <link>https://dev.to/jy_wang_2c4d36f4d66adea80/i-built-a-free-ai-instagram-bio-generator-in-10-days-heres-what-i-learned-4bp4</link>
      <guid>https://dev.to/jy_wang_2c4d36f4d66adea80/i-built-a-free-ai-instagram-bio-generator-in-10-days-heres-what-i-learned-4bp4</guid>
      <description>&lt;p&gt;I spent way too long staring at a blank Instagram bio box last month. 150 characters. How hard could it be?&lt;/p&gt;

&lt;p&gt;Turns out, pretty hard. So I built a tool to solve it.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://www.socialbiogen.net" rel="noopener noreferrer"&gt;SocialBioGen&lt;/a&gt;&lt;/strong&gt; — generate 3–5 personalized, emoji-packed Instagram&lt;br&gt;
   bios in under 3 seconds. No signup required.&lt;/p&gt;




&lt;p&gt;## The Problem&lt;/p&gt;

&lt;p&gt;Writing a 150-character bio that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Captures your personality or brand&lt;/li&gt;
&lt;li&gt;Uses emoji tastefully&lt;/li&gt;
&lt;li&gt;Actually makes people want to follow you&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...is surprisingly difficult. Most people end up with either a generic "Coffee lover ☕ | NYC" or just&lt;br&gt;
  leave it blank.&lt;/p&gt;

&lt;p&gt;I searched for tools and found mostly clunky, ad-heavy generators that required email signups just to&lt;br&gt;
  see results. So I built my own.&lt;/p&gt;




&lt;p&gt;## How It Works&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Choose your type&lt;/strong&gt; — Personal, Business, or Creator&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Describe yourself&lt;/strong&gt; in a few words&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pick a vibe&lt;/strong&gt; — Professional, Funny, Aesthetic, Minimalist, Bold, or Cute&lt;/li&gt;
&lt;li&gt;Get &lt;strong&gt;3–5 ready-to-copy bios&lt;/strong&gt; in under 3 seconds
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  Input:  "Travel lover, coffee addict, based in NYC"
  Vibe:   Funny
  Type:   Personal

  Output:
  → ✈️  Fueled by coffee &amp;amp; flight miles | NYC chaos survivor ☕
  → Professional napper between flights 🌍 NYC base, world playground
  → Lost in every city, found in every café ☕✈️  NYC HQ
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;## Tech Stack&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Next.js 15&lt;/strong&gt; (App Router) — SSR + SSG for SEO&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenRouter API&lt;/strong&gt; — LLM calls (GPT-4o-mini, ~$0.001/request)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Neon PostgreSQL&lt;/strong&gt; — rate limiting + user credits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PayPal SDK&lt;/strong&gt; — one-time payments, no subscription complexity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vercel&lt;/strong&gt; — zero-ops deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total infra cost: &lt;strong&gt;&amp;lt; $5/month&lt;/strong&gt; at moderate traffic.&lt;/p&gt;




&lt;p&gt;## The SEO Play: Programmatic Pages&lt;/p&gt;

&lt;p&gt;Beyond the main generator, I built 50+ niche landing pages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/instagram-bio-for/travel&lt;/code&gt; — Travel Instagram Bio Generator&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/instagram-bio-for/fitness&lt;/code&gt; — Fitness Instagram Bio Generator&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/instagram-bio-for/photographers&lt;/code&gt; — ...you get the idea&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each page has 20+ pre-generated bio examples + the live AI generator. Built with&lt;br&gt;
  &lt;code&gt;generateStaticParams()&lt;/code&gt; in Next.js — the entire content cost &amp;lt; $1 in API calls to generate upfront.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One bug I hit:&lt;/strong&gt; &lt;code&gt;revalidate = 3600&lt;/code&gt; (ISR) conflicted with &lt;code&gt;next-intl&lt;/code&gt;'s &lt;code&gt;setRequestLocale&lt;/code&gt;, throwing&lt;br&gt;
   a &lt;code&gt;DYNAMIC_SERVER_USAGE&lt;/code&gt; 500 error in production. Fixed by switching to &lt;code&gt;dynamic = 'force-static'&lt;/code&gt; —&lt;br&gt;
  same pattern used in the Next.js docs pages.&lt;/p&gt;




&lt;p&gt;## Monetization&lt;/p&gt;

&lt;p&gt;Free tier: 5 generations/day, no signup.&lt;/p&gt;

&lt;p&gt;Paid:&lt;br&gt;
  | Plan | Price | Credits |&lt;br&gt;
  |------|-------|---------|&lt;br&gt;
  | Starter | $4.99 | 100 |&lt;br&gt;
  | Pro | $9.99 | 300 |&lt;br&gt;
  | &lt;strong&gt;Lifetime&lt;/strong&gt; | &lt;strong&gt;$19.99&lt;/strong&gt; | &lt;strong&gt;Unlimited&lt;/strong&gt; |&lt;/p&gt;

&lt;p&gt;Credits never expire. The Lifetime deal is the anchor — once you see $9.99 vs $19.99, unlimited forever&lt;br&gt;
   feels obvious.&lt;/p&gt;




&lt;p&gt;## What I'd Do Differently&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Start with the SEO pages, not the generator.&lt;/strong&gt; The pSEO landing pages will drive organic traffic
long-term. I built the generator first and the SEO layer second.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ship earlier.&lt;/strong&gt; I spent 2 extra days polishing the UI before launch. Nobody cares about perfect
spacing on day 1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limit by browser fingerprint, not just IP.&lt;/strong&gt; Shared IPs (offices, universities) can exhaust
limits for unrelated users.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;## Try It&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;&lt;a href="https://www.socialbiogen.net" rel="noopener noreferrer"&gt;https://www.socialbiogen.net&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Free to use, no signup. Would love your feedback — especially on bio quality and edge cases where the&lt;br&gt;
  AI gives weird results.&lt;/p&gt;

&lt;p&gt;What's your Instagram bio right now? Drop it in the comments 👇&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>showdev</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>I Built a DLSS 5 GPU Compatibility Checker with Next.js 14 (and what I learned about AI rendering)</title>
      <dc:creator>visiohex</dc:creator>
      <pubDate>Wed, 18 Mar 2026 02:09:05 +0000</pubDate>
      <link>https://dev.to/jy_wang_2c4d36f4d66adea80/i-built-a-dlss-5-gpu-compatibility-checker-with-nextjs-14-and-what-i-learned-about-ai-rendering-4cjh</link>
      <guid>https://dev.to/jy_wang_2c4d36f4d66adea80/i-built-a-dlss-5-gpu-compatibility-checker-with-nextjs-14-and-what-i-learned-about-ai-rendering-4cjh</guid>
      <description>&lt;p&gt;NVIDIA announced DLSS 5 at GTC on March 16, 2026 — and the internet immediately exploded with questions: &lt;em&gt;"Does my GPU support it? Is my RTX 4090 going to be left out?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I couldn't find a clean, reliable answer anywhere. So I built one.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://dlss5.net" rel="noopener noreferrer"&gt;dlss5.net&lt;/a&gt;&lt;/strong&gt; — a free GPU compatibility checker for DLSS 5&lt;/p&gt;




&lt;p&gt;## What is DLSS 5, actually?&lt;/p&gt;

&lt;p&gt;This tripped me up while building the site. A lot of sources were confusing DLSS 5 with Multi Frame Generation (MFG) — but they're completely different things:&lt;/p&gt;

&lt;p&gt;| Version | Key Feature | Focus |&lt;br&gt;
  |---------|-------------|-------|&lt;br&gt;
  | DLSS 3 | Frame Generation (1 AI frame) | Performance |&lt;br&gt;
  | DLSS 4 | Multi Frame Generation 4X | Performance |&lt;br&gt;
  | DLSS 4.5 | Dynamic MFG 6X | Performance |&lt;br&gt;
  | &lt;strong&gt;DLSS 5&lt;/strong&gt; | &lt;strong&gt;Neural Rendering&lt;/strong&gt; | &lt;strong&gt;Visual Fidelity&lt;/strong&gt; |&lt;/p&gt;

&lt;p&gt;DLSS 5's core feature is &lt;strong&gt;Real-time Neural Rendering&lt;/strong&gt; — AI analyzes scene semantics (hair, skin, fabric, lighting) and generates photoreal material responses in real time. Jensen Huang called it &lt;em&gt;"the GPT moment for graphics."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It runs &lt;em&gt;on top of&lt;/em&gt; DLSS 4.5, not instead of it.&lt;/p&gt;




&lt;p&gt;## What I built&lt;/p&gt;

&lt;p&gt;The tool lets you type any GPU name and instantly see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DLSS 5 status&lt;/strong&gt;: Confirmed / Possible / Unlikely / Not Supported&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Current DLSS 4/4.5 performance data&lt;/strong&gt; (since DLSS 5 isn't out yet)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DLSS version comparison table&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Upgrade recommendations&lt;/strong&gt; if your GPU isn't confirmed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;## Tech stack&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Next.js 14&lt;/strong&gt; (App Router, fully static)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript&lt;/strong&gt; + &lt;strong&gt;Tailwind CSS&lt;/strong&gt; + &lt;strong&gt;shadcn/ui&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fuse.js&lt;/strong&gt; for fuzzy GPU name search&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recharts&lt;/strong&gt; for FPS benchmark charts&lt;/li&gt;
&lt;li&gt;Deployed on &lt;strong&gt;Vercel&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The GPU data lives in a JSON file — easy to update when NVIDIA releases&lt;br&gt;
  official specs closer to the Fall 2026 launch.&lt;/p&gt;




&lt;p&gt;## Interesting data decisions&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RTX 5060 / 5060 Ti&lt;/strong&gt;: I initially marked these as "Confirmed" since they're&lt;br&gt;
  RTX 50 series. But the GTC demo ran DLSS 5 on &lt;em&gt;two&lt;/em&gt; RTX 5090s — one for the game,&lt;br&gt;
  one dedicated to the AI model. The RTX 5060 has only 8GB VRAM. I changed both&lt;br&gt;
  to &lt;strong&gt;"Unlikely"&lt;/strong&gt; until NVIDIA publishes official minimum specs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Min. GPU requirement&lt;/strong&gt;: NVIDIA's official FAQ literally says &lt;em&gt;"Minimum GPU&lt;br&gt;
  specifications are pending model optimizations and will be provided closer to release."&lt;/em&gt;&lt;br&gt;
  So the table says &lt;strong&gt;TBD&lt;/strong&gt;, not "RTX 50 confirmed."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RTX 20/30 series&lt;/strong&gt;: These support DLSS 3.5 (Super Resolution + Ray Reconstruction),&lt;br&gt;
  not just Super Resolution. Ray Reconstruction shipped with DLSS 3.5 and is&lt;br&gt;
  backwards-compatible.&lt;/p&gt;




&lt;p&gt;## What's next&lt;/p&gt;

&lt;p&gt;DLSS 5 launches Fall 2026. I'll update the site as NVIDIA releases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Official minimum GPU specs&lt;/li&gt;
&lt;li&gt;Game support list updates&lt;/li&gt;
&lt;li&gt;Actual benchmark data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The site also has a &lt;strong&gt;Portuguese version&lt;/strong&gt; (&lt;a href="https://dlss5.net/pt" rel="noopener noreferrer"&gt;dlss5.net/pt&lt;/a&gt;)&lt;br&gt;
  — "dlss 5 quais placas" turned out to be a trending search query, so Brazilian&lt;br&gt;
  gamers were clearly looking for this too.&lt;/p&gt;




&lt;p&gt;If you're a gamer wondering about your GPU, check it out: &lt;strong&gt;&lt;a href="https://dlss5.net" rel="noopener noreferrer"&gt;dlss5.net&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Feedback welcome — especially if you spot any data errors. DLSS versioning is&lt;br&gt;
  genuinely confusing and I want to keep this accurate.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I Built a Free Resume Bullet Rewriter for ATS (Action Verbs for Resume)</title>
      <dc:creator>visiohex</dc:creator>
      <pubDate>Wed, 04 Mar 2026 08:28:21 +0000</pubDate>
      <link>https://dev.to/jy_wang_2c4d36f4d66adea80/i-built-a-free-resume-bullet-rewriter-for-ats-action-verbs-for-resume-2j12</link>
      <guid>https://dev.to/jy_wang_2c4d36f4d66adea80/i-built-a-free-resume-bullet-rewriter-for-ats-action-verbs-for-resume-2j12</guid>
      <description>&lt;p&gt;I recently launched &lt;strong&gt;PowerVerb&lt;/strong&gt;: &lt;a href="https://www.resumeactionverbs.com" rel="noopener noreferrer"&gt;resumeactionverbs.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s a &lt;strong&gt;resume bullet rewriter&lt;/strong&gt; focused on turning weak lines into stronger, &lt;strong&gt;ATS-friendly resume bullets&lt;/strong&gt; using better &lt;strong&gt;action verbs for resume&lt;/strong&gt; writing.&lt;/p&gt;

&lt;p&gt;If you’re searching for &lt;strong&gt;resume action verbs&lt;/strong&gt;, &lt;strong&gt;power verbs for resume&lt;/strong&gt;, or a fast way to rewrite “Responsible for…” bullets, this tool is built for that exact use case.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I built a resume bullet rewriter
&lt;/h2&gt;

&lt;p&gt;Most tools either over-generate or produce generic text.&lt;br&gt;&lt;br&gt;
I wanted a practical &lt;strong&gt;resume action verbs tool&lt;/strong&gt; that is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fast&lt;/li&gt;
&lt;li&gt;cost-efficient&lt;/li&gt;
&lt;li&gt;less likely to hallucinate&lt;/li&gt;
&lt;li&gt;useful for real job applications&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Core features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Rewrite one bullet into 3 ATS-friendly versions&lt;/li&gt;
&lt;li&gt;Suggest better &lt;strong&gt;resume action verbs by role&lt;/strong&gt; (engineering, product, marketing, sales)&lt;/li&gt;
&lt;li&gt;Detect weak/repetitive wording&lt;/li&gt;
&lt;li&gt;Save and reuse recent rewrites&lt;/li&gt;
&lt;li&gt;Daily free quota + paid credits/subscription&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Tech stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend&lt;/strong&gt;: Next.js (App Router)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DB/Auth&lt;/strong&gt;: Supabase&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rewrite engine&lt;/strong&gt;: Rule engine + OpenRouter fallback&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payments&lt;/strong&gt;: PayPal (orders + subscriptions + webhook idempotency)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploy&lt;/strong&gt;: Vercel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DNS/CDN&lt;/strong&gt;: Cloudflare&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why “rule-first + AI-assisted”?
&lt;/h2&gt;

&lt;p&gt;Initially, I used AI for everything. It worked, but costs and latency were unstable, and weak-verb checks were overkill for LLMs.&lt;/p&gt;

&lt;p&gt;So I split the system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic rules&lt;/strong&gt; for repetition checking + weak verb detection
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI&lt;/strong&gt; only for rewrite generation (with strict constraints)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This reduced token usage and made UX feel much faster.&lt;/p&gt;




&lt;h2&gt;
  
  
  Output quality guardrails
&lt;/h2&gt;

&lt;p&gt;The rewrite prompt enforces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;don’t invent numbers&lt;/li&gt;
&lt;li&gt;don’t invent responsibilities&lt;/li&gt;
&lt;li&gt;keep original facts&lt;/li&gt;
&lt;li&gt;return strict JSON schema&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then I post-process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dedupe variations&lt;/li&gt;
&lt;li&gt;enforce exactly 3 outputs&lt;/li&gt;
&lt;li&gt;fallback to local rule rewrite when model output is invalid&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This combo dramatically improved consistency.&lt;/p&gt;




&lt;h2&gt;
  
  
  Credits/quota logic (important lesson)
&lt;/h2&gt;

&lt;p&gt;One subtle bug I fixed: deduction order.&lt;/p&gt;

&lt;p&gt;I originally deducted paid credits first, which felt wrong for users with daily free quota.&lt;br&gt;&lt;br&gt;
Now the order is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;daily free quota
&lt;/li&gt;
&lt;li&gt;subscription credits
&lt;/li&gt;
&lt;li&gt;purchased credits&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That single logic change removed a lot of confusion and support issues.&lt;/p&gt;




&lt;h2&gt;
  
  
  UX fixes that mattered more than expected
&lt;/h2&gt;

&lt;p&gt;Two tiny changes improved trust a lot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Copy button feedback&lt;/strong&gt; (&lt;code&gt;Copy&lt;/code&gt; -&amp;gt; &lt;code&gt;Copied&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reuse Output&lt;/strong&gt; auto-scrolls back to editor and shows &lt;code&gt;Loaded&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without visible feedback, users think the app is broken—even when logic works.&lt;/p&gt;




&lt;h2&gt;
  
  
  SEO and launch notes
&lt;/h2&gt;

&lt;p&gt;I shipped:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;role-based long-tail landing pages&lt;/li&gt;
&lt;li&gt;sitemap + robots&lt;/li&gt;
&lt;li&gt;canonical + OG metadata&lt;/li&gt;
&lt;li&gt;favicon + structured metadata cleanup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then submitted sitemap to Google Search Console and started testing channels like HN Show.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I’d improve next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;JD-aware rewrite mode (align bullet to job description)&lt;/li&gt;
&lt;li&gt;batch rewrite mode&lt;/li&gt;
&lt;li&gt;better analytics mapping (GA4 event taxonomy + funnel reporting)&lt;/li&gt;
&lt;li&gt;export formats (DOCX/Notion/JSON)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;If you’re building an AI writing product, my biggest takeaway is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Put deterministic logic in front of AI whenever possible.&lt;br&gt;&lt;br&gt;
It makes your product cheaper, faster, and easier to trust.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you want, I can share a follow-up post with my exact event taxonomy and payment webhook idempotency setup.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>nextjs</category>
      <category>supabase</category>
    </item>
    <item>
      <title>How I Built an AI-Powered APA Citation Generator with Next.js, Supabase, and PayPal</title>
      <dc:creator>visiohex</dc:creator>
      <pubDate>Thu, 26 Feb 2026 08:12:24 +0000</pubDate>
      <link>https://dev.to/jy_wang_2c4d36f4d66adea80/how-i-built-an-ai-powered-apa-citation-generator-with-nextjs-supabase-and-paypal-3io7</link>
      <guid>https://dev.to/jy_wang_2c4d36f4d66adea80/how-i-built-an-ai-powered-apa-citation-generator-with-nextjs-supabase-and-paypal-3io7</guid>
      <description>&lt;p&gt;If you have ever tried to cite random web pages in APA style, you know the pain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;missing authors&lt;/li&gt;
&lt;li&gt;missing publication dates&lt;/li&gt;
&lt;li&gt;inconsistent metadata&lt;/li&gt;
&lt;li&gt;lots of manual fixing after "auto generation"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I built &lt;strong&gt;APA Citation Generator&lt;/strong&gt; to solve that specific workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;accept a URL or DOI&lt;/li&gt;
&lt;li&gt;extract metadata with rule-first parsing&lt;/li&gt;
&lt;li&gt;use AI only for missing fields&lt;/li&gt;
&lt;li&gt;output APA reference + in-text citation&lt;/li&gt;
&lt;li&gt;clearly mark confidence and review warnings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Live demo: &lt;a href="https://apacitationgenerator.online" rel="noopener noreferrer"&gt;https://apacitationgenerator.online&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built this
&lt;/h2&gt;

&lt;p&gt;Most citation tools work great on clean sources, but fail on real-world pages. In practice, users still need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;open the page manually&lt;/li&gt;
&lt;li&gt;find author/date by hand&lt;/li&gt;
&lt;li&gt;patch placeholders like &lt;code&gt;(n.d.)&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;My goal was not "generate everything with AI".&lt;br&gt;
My goal was to build a &lt;strong&gt;fast, reviewable citation pipeline&lt;/strong&gt; where users can trust what is extracted and quickly fix what is uncertain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend/App&lt;/strong&gt;: Next.js 16 (App Router)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DB/Auth&lt;/strong&gt;: Supabase (custom &lt;code&gt;apa&lt;/code&gt; schema, Google OAuth)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI&lt;/strong&gt;: OpenRouter (Gemini model fallback chain)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payments&lt;/strong&gt;: PayPal (one-time credit packs + subscription)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploy&lt;/strong&gt;: Vercel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Domain/DNS&lt;/strong&gt;: Cloudflare&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Core architecture
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1) Input classification
&lt;/h3&gt;

&lt;p&gt;User input is classified as either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;URL&lt;/li&gt;
&lt;li&gt;DOI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Anything else is rejected early with explicit errors.&lt;/p&gt;

&lt;h3&gt;
  
  
  2) Rule-first metadata extraction
&lt;/h3&gt;

&lt;p&gt;For URLs, I first parse metadata from HTML:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;og:title&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;og:site_name&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;article:published_time&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;common author/date tags&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For DOI, I query Crossref.&lt;/p&gt;

&lt;h3&gt;
  
  
  3) AI completion only for missing fields
&lt;/h3&gt;

&lt;p&gt;If critical fields are missing, I send a trimmed text snapshot to AI and ask for strict JSON output:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;authors&lt;/li&gt;
&lt;li&gt;publicationDate&lt;/li&gt;
&lt;li&gt;title&lt;/li&gt;
&lt;li&gt;containerTitle&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This kept latency low and reduced hallucination risk versus full-AI generation.&lt;/p&gt;

&lt;h3&gt;
  
  
  4) APA formatter + confidence model
&lt;/h3&gt;

&lt;p&gt;The formatter creates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;full reference&lt;/li&gt;
&lt;li&gt;in-text citation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then a confidence score is calculated from available fields and inference flags.&lt;br&gt;
Low-confidence outputs are labeled &lt;strong&gt;Needs Review&lt;/strong&gt; with specific warnings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monetization model
&lt;/h2&gt;

&lt;p&gt;I used a mixed model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;free daily quota&lt;/li&gt;
&lt;li&gt;paid credit packs&lt;/li&gt;
&lt;li&gt;monthly Pro subscription&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This works better than a hard paywall for utility tools.&lt;br&gt;
Users can test value first, then upgrade for volume.&lt;/p&gt;

&lt;h2&gt;
  
  
  SEO strategy (early stage)
&lt;/h2&gt;

&lt;p&gt;Primary keyword: &lt;strong&gt;"APA Citation Generator"&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Then I added related landing pages and long-tail clusters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no author citation&lt;/li&gt;
&lt;li&gt;no date citation&lt;/li&gt;
&lt;li&gt;DOI to APA&lt;/li&gt;
&lt;li&gt;in-text citation guides&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also shipped:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sitemap&lt;/li&gt;
&lt;li&gt;robots&lt;/li&gt;
&lt;li&gt;metadata/canonical&lt;/li&gt;
&lt;li&gt;structured data (FAQ/Breadcrumb/SoftwareApplication)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I had to harden before launch
&lt;/h2&gt;

&lt;p&gt;After initial release, I did a security pass and fixed a few important issues:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;SSRF protections&lt;/strong&gt; on URL fetch&lt;/li&gt;
&lt;li&gt;block localhost/internal hosts/private IP ranges&lt;/li&gt;
&lt;li&gt;restrict ports&lt;/li&gt;
&lt;li&gt;&lt;p&gt;safe redirect handling&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;OAuth callback open redirect&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;allow only internal &lt;code&gt;next&lt;/code&gt; paths&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Edit API authorization&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;verify auth + ownership before updating saved citation jobs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Quota/credit race conditions&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;switched to compare-and-set style updates for concurrent safety&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CSP + security headers&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;nonce-based CSP&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;frame/content/referrer protections&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Lead form anti-bot&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;honeypot + timing checks + per-IP daily limit&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;optional Cloudflare Turnstile support&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Lessons learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Rule-first + AI fallback beats AI-only&lt;/strong&gt; for this use case.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explain uncertainty&lt;/strong&gt; in UI. Users accept imperfection when they can see why.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monetization should follow workflow friction&lt;/strong&gt;, not just pageviews.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security hardening early&lt;/strong&gt; saves painful migrations later.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Current limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;still focused on URL + DOI flows&lt;/li&gt;
&lt;li&gt;some edge cases need manual verification (as expected in citation work)&lt;/li&gt;
&lt;li&gt;content and internal-link SEO still improving&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What’s next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;better source-specific parsing heuristics&lt;/li&gt;
&lt;li&gt;export formats (BibTeX/Word)&lt;/li&gt;
&lt;li&gt;stronger account history and saved references UX&lt;/li&gt;
&lt;li&gt;deeper analytics for conversion and retention&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you build in the education/productivity SEO space, I’d love feedback on two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Which citation edge cases break most often in your experience?&lt;/li&gt;
&lt;li&gt;Which growth loop would you prioritize first: browser extension, exports, or team collaboration?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I’m happy to share implementation details if anyone wants to replicate this architecture.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>nextjs</category>
      <category>showdev</category>
      <category>supabase</category>
    </item>
  </channel>
</rss>
