Kemp.rest / Examples / Code / llms.txt generator

KEMP.REST / EXAMPLES / CODE

llms.txt generator

Written by Kevin Kemp in HTML, CSS, and JavaScript for PressCare. A public AI SEO tool: the llms.txt file is built in the browser.

View the code

This is the llms.txt tool in the Tools menu on presscare.com, for AI SEO. It writes the short markdown file that tells an assistant what the site is and which pages matter. Nothing is crawled and nothing leaves the browser. AnswerPins writes the same kind of file from the pages it has published and sends that file to the site.

Type of code

A self-contained code block. The script holds the form state, renders the file, and runs the checks beside it.

Technology

HTML, CSS, and JavaScript. The output follows the llmstxt.org layout: an H1 name, a blockquote summary, then grouped links. Pasted paths are turned into absolute URLs from the home page field. There is no library and no request.

What it does

Problem it solved

A site needed a plain map for assistants and browsing agents, without a crawl, an account, or a plugin that wraps the file in a theme.

Code

Back to summary · presscare.com/free-online-llms-txt-creator/

<style>
.ltxg-root{
  /* dark AI-section tone */
  --ltxg-ink:#061634;
  --ltxg-panel:#0B2350;
  --ltxg-panel-hi:#123063;
  --ltxg-field:#081D42;
  --ltxg-line:rgba(237,234,227,.15);
  --ltxg-line-soft:rgba(237,234,227,.08);
  --ltxg-fg:#EDEAE3;
  --ltxg-fg-dim:#9AACC9;
  --ltxg-gold:#F6B210;
  --ltxg-gold-ink:#09255C;
  --ltxg-paper:#F9F6F1;
  --ltxg-paper-ink:#09255C;
  --ltxg-paper-dim:#5B6472;
  --ltxg-flag-warn:#F6B210;
  --ltxg-flag-bad:#E8765A;
  --ltxg-flag-ok:#8FA6C4;
  --ltxg-r:12px;
  --ltxg-r-sm:8px;
  --ltxg-mono:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace;

  box-sizing:border-box;
  display:block;
  background:
    radial-gradient(120% 90% at 8% 0%, #0D2A5E 0%, rgba(13,42,94,0) 58%),
    linear-gradient(178deg,#071B45 0%,#061634 100%);
  color:var(--ltxg-fg);
  border-radius:var(--ltxg-r);
  padding:clamp(20px,3.4vw,44px);
  font-family:inherit;
  line-height:1.55;
  -webkit-font-smoothing:antialiased;
}
.ltxg-root.ltxg-light{
  --ltxg-ink:#F9F6F1;
  --ltxg-panel:#FFFFFF;
  --ltxg-panel-hi:#F5F1EA;
  --ltxg-field:#FFFFFF;
  --ltxg-line:rgba(9,37,92,.16);
  --ltxg-line-soft:rgba(9,37,92,.09);
  --ltxg-fg:#09255C;
  --ltxg-fg-dim:#5B6472;
  --ltxg-paper:#FFFFFF;
  --ltxg-flag-ok:#5B6472;
  background:linear-gradient(178deg,#F9F6F1 0%,#F5F1EA 100%);
}
.ltxg-root *,
.ltxg-root *::before,
.ltxg-root *::after{box-sizing:border-box;}

/* ---------- masthead ---------- */
.ltxg-top{max-width:62ch;margin:0 0 clamp(22px,3vw,34px);}
.ltxg-root h2.ltxg-h{
  font-size:clamp(1.5rem,1.05rem + 1.5vw,2.2rem);
  line-height:1.15;letter-spacing:-.015em;font-weight:700;
  color:var(--ltxg-fg);margin:0 0 .5rem;
}
.ltxg-sub{color:var(--ltxg-fg-dim);margin:0;font-size:1rem;}
.ltxg-sub a{color:var(--ltxg-gold);text-decoration:underline;text-underline-offset:3px;}
.ltxg-sub a:hover{color:var(--ltxg-gold);opacity:.82;}

/* ---------- two-column frame ---------- */
.ltxg-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:clamp(20px,2.6vw,30px);}
@media(min-width:1000px){
  .ltxg-grid{grid-template-columns:minmax(0,1.04fr) minmax(0,.96fr);align-items:start;}
  .ltxg-out{position:sticky;top:120px;}
}

/* ---------- builder ---------- */
.ltxg-block{
  background:var(--ltxg-panel);
  border:1px solid var(--ltxg-line);
  border-radius:var(--ltxg-r);
  padding:clamp(16px,2vw,24px);
}
.ltxg-block + .ltxg-block{margin-top:16px;}
.ltxg-root h3.ltxg-bh{
  font-size:1.02rem;font-weight:700;letter-spacing:-.005em;
  color:var(--ltxg-fg);margin:0 0 .2rem;
}
.ltxg-bnote{color:var(--ltxg-fg-dim);font-size:.87rem;margin:0 0 1rem;max-width:58ch;}
.ltxg-bnote code{
  font-family:var(--ltxg-mono);font-size:.84em;
  background:var(--ltxg-panel-hi);color:var(--ltxg-gold);
  padding:1px 6px;border-radius:4px;
}

.ltxg-field{margin-bottom:14px;}
.ltxg-field:last-child{margin-bottom:0;}
.ltxg-root label.ltxg-lab{
  display:block;font-size:.83rem;font-weight:600;
  color:var(--ltxg-fg);margin:0 0 5px;
}
.ltxg-req{color:var(--ltxg-gold);font-weight:600;}
.ltxg-hint{display:block;font-weight:400;color:var(--ltxg-fg-dim);font-size:.79rem;margin-top:2px;}

.ltxg-root input[type=text],
.ltxg-root input[type=url],
.ltxg-root textarea{
  width:100%;
  background:var(--ltxg-field);
  color:var(--ltxg-fg);
  border:1px solid var(--ltxg-line);
  border-radius:var(--ltxg-r-sm);
  padding:10px 12px;
  font-family:inherit;font-size:.93rem;line-height:1.45;
  margin:0;min-height:0;box-shadow:none;
  transition:border-color .14s ease, background .14s ease;
}
.ltxg-root textarea{resize:vertical;min-height:84px;}
.ltxg-root input[type=text]::placeholder,
.ltxg-root input[type=url]::placeholder,
.ltxg-root textarea::placeholder{color:var(--ltxg-fg-dim);opacity:.7;}
.ltxg-root input[type=text]:hover,
.ltxg-root input[type=url]:hover,
.ltxg-root textarea:hover{border-color:rgba(246,178,16,.4);}
.ltxg-root input[type=text]:focus,
.ltxg-root input[type=url]:focus,
.ltxg-root textarea:focus{
  outline:none;border-color:var(--ltxg-gold);background:var(--ltxg-panel-hi);
}
.ltxg-root :focus-visible{outline:2px solid var(--ltxg-gold);outline-offset:2px;}

/* ---------- sections ---------- */
.ltxg-sec{
  border:1px solid var(--ltxg-line);
  border-radius:var(--ltxg-r-sm);
  background:var(--ltxg-panel-hi);
  padding:14px;margin-bottom:14px;
}
.ltxg-sec[data-optional="1"]{border-color:rgba(246,178,16,.45);}
.ltxg-sechead{display:flex;gap:10px;align-items:flex-end;flex-wrap:wrap;margin-bottom:4px;}
.ltxg-sechead .ltxg-field{flex:1 1 180px;margin-bottom:0;}
.ltxg-optnote{
  color:var(--ltxg-fg-dim);font-size:.79rem;margin:8px 0 0;
  border-left:2px solid var(--ltxg-gold);padding-left:9px;max-width:56ch;
}
.ltxg-link{
  display:grid;grid-template-columns:minmax(0,1fr);gap:8px;
  padding:12px 0;border-top:1px solid var(--ltxg-line-soft);
}
@media(min-width:620px){
  .ltxg-link{grid-template-columns:minmax(0,.9fr) minmax(0,1.1fr) minmax(0,1.2fr) auto;align-items:center;}
}
.ltxg-link .ltxg-lab{font-size:.75rem;color:var(--ltxg-fg-dim);font-weight:500;}
@media(min-width:620px){
  .ltxg-link .ltxg-lab{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;}
  .ltxg-link:first-of-type .ltxg-lab{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal;}
}

/* ---------- checkbox ---------- */
.ltxg-check{display:flex;align-items:center;gap:8px;font-size:.85rem;color:var(--ltxg-fg);cursor:pointer;}
.ltxg-root input[type=checkbox].ltxg-cb{
  appearance:none;-webkit-appearance:none;
  width:17px;height:17px;flex:0 0 17px;margin:0;
  border:1px solid var(--ltxg-line);border-radius:4px;
  background:var(--ltxg-field);cursor:pointer;position:relative;
}
.ltxg-root input[type=checkbox].ltxg-cb:checked{background:var(--ltxg-gold);border-color:var(--ltxg-gold);}
.ltxg-root input[type=checkbox].ltxg-cb:checked::after{
  content:"";position:absolute;left:5px;top:1px;width:5px;height:10px;
  border:solid var(--ltxg-gold-ink);border-width:0 2px 2px 0;transform:rotate(42deg);
}

/* ---------- buttons ---------- */
.ltxg-root button{
  font-family:inherit;cursor:pointer;border-radius:var(--ltxg-r-sm);
  transition:background .14s ease,border-color .14s ease,color .14s ease,opacity .14s ease;
}
.ltxg-btn{
  background:var(--ltxg-gold);color:var(--ltxg-gold-ink);
  border:1px solid var(--ltxg-gold);
  font-weight:700;font-size:.92rem;padding:11px 20px;
}
.ltxg-btn:hover{background:#FFC63A;border-color:#FFC63A;}
.ltxg-btn[disabled]{opacity:.45;cursor:not-allowed;}
.ltxg-btn-2{
  background:transparent;color:var(--ltxg-fg);
  border:1px solid var(--ltxg-line);
  font-weight:600;font-size:.92rem;padding:11px 20px;
}
.ltxg-btn-2:hover{border-color:var(--ltxg-gold);color:var(--ltxg-gold);}
.ltxg-btn-3{
  background:transparent;color:var(--ltxg-fg-dim);
  border:1px dashed var(--ltxg-line);
  font-weight:600;font-size:.83rem;padding:8px 14px;
}
.ltxg-btn-3:hover{border-color:var(--ltxg-gold);border-style:solid;color:var(--ltxg-gold);}
.ltxg-x{
  background:transparent;border:1px solid var(--ltxg-line);color:var(--ltxg-fg-dim);
  width:32px;height:32px;flex:0 0 32px;font-size:1.05rem;line-height:1;padding:0;
}
.ltxg-x:hover{border-color:var(--ltxg-flag-bad);color:var(--ltxg-flag-bad);}
.ltxg-rowbtns{display:flex;gap:9px;flex-wrap:wrap;margin-top:12px;}

/* ---------- the file ---------- */
.ltxg-filetab{
  display:inline-flex;align-items:center;gap:8px;
  background:var(--ltxg-paper);color:var(--ltxg-paper-ink);
  font-family:var(--ltxg-mono);font-size:.8rem;font-weight:600;
  padding:6px 14px;border-radius:var(--ltxg-r-sm) var(--ltxg-r-sm) 0 0;
}
.ltxg-dot{width:7px;height:7px;border-radius:50%;background:var(--ltxg-gold);}
.ltxg-paper{
  background:var(--ltxg-paper);
  border-radius:0 var(--ltxg-r) var(--ltxg-r) var(--ltxg-r);
  padding:18px 20px;max-height:min(58vh,560px);overflow:auto;
}
.ltxg-pre{
  margin:0;font-family:var(--ltxg-mono);font-size:.845rem;line-height:1.72;
  color:var(--ltxg-paper-ink);white-space:pre-wrap;word-break:break-word;
  background:none;border:none;padding:0;
}
.ltxg-meta{
  display:flex;gap:6px;flex-wrap:wrap;align-items:center;
  color:var(--ltxg-fg-dim);font-size:.8rem;margin:10px 0 0;
  font-family:var(--ltxg-mono);
}
.ltxg-acts{display:flex;gap:10px;flex-wrap:wrap;margin-top:14px;}

/* ---------- checks ---------- */
.ltxg-checks{margin-top:18px;}
.ltxg-checks ul{list-style:none;margin:0;padding:0;}
.ltxg-checks li{
  display:flex;gap:10px;align-items:flex-start;
  font-size:.855rem;color:var(--ltxg-fg-dim);
  padding:7px 0;border-top:1px solid var(--ltxg-line-soft);
}
.ltxg-checks li:first-child{border-top:none;}
.ltxg-flag{width:8px;height:8px;border-radius:50%;flex:0 0 8px;margin-top:7px;background:var(--ltxg-flag-ok);}
.ltxg-flag.warn{background:var(--ltxg-flag-warn);}
.ltxg-flag.bad{background:var(--ltxg-flag-bad);}
.ltxg-checks li.bad{color:var(--ltxg-fg);}

/* ---------- shared section rhythm ---------- */
.ltxg-band{
  margin-top:clamp(30px,4vw,52px);
  padding-top:clamp(26px,3vw,38px);
  border-top:1px solid var(--ltxg-line);
}
.ltxg-root h3.ltxg-sh{
  font-size:clamp(1.18rem,1rem + .6vw,1.5rem);
  font-weight:700;letter-spacing:-.01em;color:var(--ltxg-fg);margin:0 0 .45rem;
}
.ltxg-slead{color:var(--ltxg-fg-dim);font-size:.94rem;margin:0 0 clamp(18px,2.4vw,28px);max-width:60ch;}

/* ---------- how it works ---------- */
.ltxg-steps{display:grid;gap:16px;grid-template-columns:minmax(0,1fr);}
@media(min-width:700px){.ltxg-steps{grid-template-columns:repeat(2,minmax(0,1fr));gap:18px 26px;}}
@media(min-width:1100px){.ltxg-steps{grid-template-columns:repeat(4,minmax(0,1fr));}}
.ltxg-step{display:grid;grid-template-columns:auto minmax(0,1fr);gap:14px;align-items:start;}
.ltxg-num{
  font-family:var(--ltxg-mono);font-size:1.05rem;font-weight:700;
  color:var(--ltxg-gold);line-height:1;
  border:1px solid rgba(246,178,16,.4);border-radius:50%;
  width:34px;height:34px;flex:0 0 34px;
  display:flex;align-items:center;justify-content:center;
}
.ltxg-step h4{font-size:.97rem;font-weight:700;color:var(--ltxg-fg);margin:.35rem 0 .3rem;}
.ltxg-step p{color:var(--ltxg-fg-dim);font-size:.875rem;margin:0;max-width:42ch;}
.ltxg-step code{
  font-family:var(--ltxg-mono);font-size:.84em;
  background:var(--ltxg-panel);color:var(--ltxg-gold);
  padding:1px 6px;border-radius:4px;word-break:break-all;
}

/* ---------- faq ---------- */
.ltxg-faq{display:grid;gap:10px;max-width:74ch;}
.ltxg-q{
  background:var(--ltxg-panel);
  border:1px solid var(--ltxg-line);
  border-radius:var(--ltxg-r-sm);
  overflow:hidden;
}
.ltxg-q[open]{border-color:rgba(246,178,16,.42);}
.ltxg-root .ltxg-q summary{
  list-style:none;cursor:pointer;
  display:flex;gap:14px;align-items:flex-start;justify-content:space-between;
  padding:15px 18px;
  font-size:.96rem;font-weight:600;color:var(--ltxg-fg);
}
.ltxg-root .ltxg-q summary::-webkit-details-marker{display:none;}
.ltxg-root .ltxg-q summary::marker{content:"";}
.ltxg-root .ltxg-q summary:hover{color:var(--ltxg-gold);}
.ltxg-sign{
  flex:0 0 14px;width:14px;height:14px;position:relative;margin-top:5px;
}
.ltxg-sign::before,.ltxg-sign::after{
  content:"";position:absolute;background:var(--ltxg-gold);
  transition:transform .16s ease,opacity .16s ease;
}
.ltxg-sign::before{left:0;top:6px;width:14px;height:2px;}
.ltxg-sign::after{left:6px;top:0;width:2px;height:14px;}
.ltxg-q[open] .ltxg-sign::after{transform:scaleY(0);opacity:0;}
.ltxg-qa{padding:0 18px 17px;color:var(--ltxg-fg-dim);font-size:.9rem;max-width:66ch;}
.ltxg-qa p{margin:0 0 .7rem;}
.ltxg-qa p:last-child{margin-bottom:0;}
.ltxg-qa code{
  font-family:var(--ltxg-mono);font-size:.85em;
  background:var(--ltxg-panel-hi);color:var(--ltxg-gold);
  padding:1px 6px;border-radius:4px;
}
.ltxg-qa strong{color:var(--ltxg-fg);font-weight:600;}

/* ---------- cta ---------- */
.ltxg-cta{
  background:var(--ltxg-panel);border:1px solid var(--ltxg-line);
  border-left:3px solid var(--ltxg-gold);
  border-radius:var(--ltxg-r-sm);padding:clamp(20px,2.4vw,28px);
  display:grid;gap:18px;align-items:center;
}
@media(min-width:800px){.ltxg-cta{grid-template-columns:minmax(0,1fr) auto;gap:30px;}}
.ltxg-cta h3{font-size:1.08rem;font-weight:700;margin:0 0 .5rem;color:var(--ltxg-fg);}
.ltxg-cta p{color:var(--ltxg-fg-dim);font-size:.91rem;margin:0;max-width:54ch;}
.ltxg-cta a.ltxg-ctabtn{
  display:inline-block;white-space:nowrap;
  background:var(--ltxg-gold);color:var(--ltxg-gold-ink);
  font-weight:700;font-size:.93rem;padding:13px 24px;
  border-radius:var(--ltxg-r-sm);text-decoration:none;
}
.ltxg-cta a.ltxg-ctabtn:hover{background:#FFC63A;color:var(--ltxg-gold-ink);}

.ltxg-toast{
  position:fixed;left:50%;bottom:28px;transform:translateX(-50%) translateY(12px);
  background:var(--ltxg-gold);color:var(--ltxg-gold-ink);
  font-weight:700;font-size:.88rem;padding:11px 20px;border-radius:var(--ltxg-r-sm);
  opacity:0;pointer-events:none;transition:opacity .18s ease,transform .18s ease;z-index:99999;
}
.ltxg-toast.on{opacity:1;transform:translateX(-50%) translateY(0);}
@media(prefers-reduced-motion:reduce){
  .ltxg-root *,.ltxg-toast{transition:none !important;}
}
</style>

<div class="ltxg-root" id="ltxg-app">

  <div class="ltxg-top">
    <h2 class="ltxg-h">llms.txt generator</h2>
    <p class="ltxg-sub">Build the file that tells AI assistants what your site is and which pages matter. Follows the <a href="https://llmstxt.org/" target="_blank" rel="noopener">llmstxt.org</a> spec. Nothing leaves your browser.</p>
  </div>

  <div class="ltxg-grid">

    <!-- BUILDER -->
    <div class="ltxg-build">

      <div class="ltxg-block">
        <h3 class="ltxg-bh">What the site is</h3>
        <p class="ltxg-bnote">The name is the only part the spec requires. The summary is the line an assistant reads first, so make it say what you do and who for.</p>

        <div class="ltxg-field">
          <label class="ltxg-lab" for="ltxg-name">Site or business name <span class="ltxg-req">required</span></label>
          <input type="text" id="ltxg-name" data-k="name" placeholder="PressCare+">
        </div>

        <div class="ltxg-field">
          <label class="ltxg-lab" for="ltxg-url">Home page URL
            <span class="ltxg-hint">Used to turn pasted paths into full links.</span>
          </label>
          <input type="url" id="ltxg-url" data-k="url" placeholder="https://presscare.com">
        </div>

        <div class="ltxg-field">
          <label class="ltxg-lab" for="ltxg-summary">One-line summary</label>
          <input type="text" id="ltxg-summary" data-k="summary" placeholder="WordPress care plans, security and migrations for small businesses.">
        </div>

        <div class="ltxg-field">
          <label class="ltxg-lab" for="ltxg-notes">Extra context
            <span class="ltxg-hint">Service areas, what you don't do, how to get in touch. Plain sentences.</span>
          </label>
          <textarea id="ltxg-notes" data-k="notes" rows="3" placeholder="Based in Denver, working with clients across the US. Every plan includes backups, updates and uptime monitoring."></textarea>
        </div>
      </div>

      <div class="ltxg-block">
        <h3 class="ltxg-bh">Pages worth pointing at</h3>
        <p class="ltxg-bnote">Group them the way a person would: services, guides, support. Keep the list short — this is a map, not a sitemap.</p>
        <div id="ltxg-sections"></div>
        <div class="ltxg-rowbtns">
          <button type="button" class="ltxg-btn-3" data-act="add-section">Add a group</button>
          <button type="button" class="ltxg-btn-3" data-act="add-optional">Add the Optional group</button>
        </div>
      </div>

      <div class="ltxg-block">
        <h3 class="ltxg-bh">Paste a batch of URLs</h3>
        <p class="ltxg-bnote">One per line. Add a title and description with pipes if you have them: <code>/pricing | Pricing | What each plan costs</code>. Titles you leave blank get built from the slug.</p>
        <div class="ltxg-field">
          <textarea id="ltxg-bulk" rows="4" placeholder="/wordpress-care-plans | Care plans | Monthly maintenance, backups and security
/free-migration
/contact"></textarea>
        </div>
        <div class="ltxg-rowbtns">
          <button type="button" class="ltxg-btn-2" data-act="bulk">Add these to the last group</button>
        </div>
      </div>

    </div>

    <!-- OUTPUT -->
    <div class="ltxg-out">
      <div class="ltxg-filetab"><span class="ltxg-dot"></span>llms.txt</div>
      <div class="ltxg-paper"><pre class="ltxg-pre" id="ltxg-preview"></pre></div>

      <p class="ltxg-meta" id="ltxg-meta"></p>

      <div class="ltxg-acts">
        <button type="button" class="ltxg-btn" data-act="download">Download llms.txt</button>
        <button type="button" class="ltxg-btn-2" data-act="copy">Copy file</button>
      </div>

      <div class="ltxg-checks">
        <ul id="ltxg-checks" aria-live="polite"></ul>
      </div>
    </div>

  </div>


  <!-- ========== HOW IT WORKS ========== -->
  <div class="ltxg-band">
    <h3 class="ltxg-sh">How it works</h3>
    <p class="ltxg-slead">Four steps, all of them in your browser. No account, no crawl of your site, no waiting.</p>

    <div class="ltxg-steps">

      <div class="ltxg-step">
        <span class="ltxg-num">1</span>
        <div>
          <h4>Describe the site</h4>
          <p>Your name and one sentence about what you do. The name is the only thing the spec insists on. The summary is the line a model is most likely to repeat back, so write it the way you would answer someone at a party.</p>
        </div>
      </div>

      <div class="ltxg-step">
        <span class="ltxg-num">2</span>
        <div>
          <h4>Add the pages that matter</h4>
          <p>Group your links the way a visitor thinks: services, guides, support. Have a list already? Paste it in and the generator builds readable titles from the slugs.</p>
        </div>
      </div>

      <div class="ltxg-step">
        <span class="ltxg-num">3</span>
        <div>
          <h4>Read the checks</h4>
          <p>The panel beside the file flags what would actually break it — no name, half-written URLs, duplicates, groups with nothing in them. Clear the red ones before you ship.</p>
        </div>
      </div>

      <div class="ltxg-step">
        <span class="ltxg-num">4</span>
        <div>
          <h4>Put it live</h4>
          <p>Download the file and drop it in your web root so it answers at <code>yoursite.com/llms.txt</code>. Load that address in a private window: plain text means it worked.</p>
        </div>
      </div>

    </div>
  </div>


  <!-- ========== FAQ ========== -->
  <div class="ltxg-band">
    <h3 class="ltxg-sh">Questions people actually ask</h3>
    <p class="ltxg-slead">Straight answers, including the ones that argue against bothering.</p>

    <div class="ltxg-faq">

      <details class="ltxg-q">
        <summary>What is an llms.txt file?<span class="ltxg-sign"></span></summary>
        <div class="ltxg-qa">
          <p>A short markdown file at the root of your domain that says what your site is and which pages are worth reading. Rather than making a model wade through your menus, cookie banner and footer, it hands over a plain list: here is who we are, here are the pages that matter, here is what each one covers.</p>
          <p>Jeremy Howard of Answer.AI proposed the format in September 2024. It is deliberately simple — a heading, a summary line, and grouped lists of links.</p>
        </div>
      </details>

      <details class="ltxg-q">
        <summary>Does it actually do anything?<span class="ltxg-sign"></span></summary>
        <div class="ltxg-qa">
          <p>Less than the hype claims, and more than the dismissals allow. Google has said on the record that it does not use the file and has no plans to. No major AI provider has confirmed reading llms.txt from other people's sites in production, and studies through 2026 found the large majority of published files are never requested at all.</p>
          <p>Where it earns its place is agent navigation. AI coding assistants and browsing agents that land on your site use it to find the right page without chewing through your HTML — which is exactly why companies like Anthropic, OpenAI, Stripe and Vercel publish one for their own docs.</p>
          <p><strong>Our honest read:</strong> it costs twenty minutes and might help later. Treat it as cheap insurance, not a growth lever.</p>
        </div>
      </details>

      <details class="ltxg-q">
        <summary>Will it improve my Google rankings?<span class="ltxg-sign"></span></summary>
        <div class="ltxg-qa">
          <p>No. Google has been explicit that llms.txt plays no part in Search or in its AI answers, and its own guidance lists the file among things site owners can skip. Rankings still come from useful content, sound technical SEO and structured data.</p>
          <p>If someone is selling you llms.txt as an AI-visibility shortcut, that is the part to be suspicious of.</p>
        </div>
      </details>

      <details class="ltxg-q">
        <summary>Is this the same as robots.txt?<span class="ltxg-sign"></span></summary>
        <div class="ltxg-qa">
          <p>No, and the difference catches people out. <code>robots.txt</code> controls what a crawler is allowed to fetch. <code>llms.txt</code> only describes what your site contains — it grants nothing and blocks nothing.</p>
          <p>Publishing one will not stop any AI company from training on your pages. If that is your goal, <code>robots.txt</code> is the file you want, and blocking the AI crawlers by name is how you do it.</p>
        </div>
      </details>

      <details class="ltxg-q">
        <summary>What is llms-full.txt?<span class="ltxg-sign"></span></summary>
        <div class="ltxg-qa">
          <p>The heavier sibling. Instead of links, it contains the actual text of your pages in one file, so a model can read everything in a single request. Useful for documentation sites and knowledge bases where the content is the product.</p>
          <p>For a typical business or service site, the index file this tool builds is the right one. A full-text dump of a twelve-page brochure site helps nobody.</p>
        </div>
      </details>

      <details class="ltxg-q">
        <summary>Where exactly does it go on a WordPress site?<span class="ltxg-sign"></span></summary>
        <div class="ltxg-qa">
          <p>In the same folder your homepage is served from — usually <code>public_html</code>, <code>htdocs</code> or <code>www</code>, depending on the host. It sits alongside <code>robots.txt</code>. Upload it over SFTP or through your host's file manager.</p>
          <p>It cannot be a WordPress page or post. Those get rendered as HTML through your theme, which defeats the whole point. If you visit the address and see your header and footer, it is in the wrong place.</p>
        </div>
      </details>

      <details class="ltxg-q">
        <summary>How many pages should I list?<span class="ltxg-sign"></span></summary>
        <div class="ltxg-qa">
          <p>Fewer than you think. A map is useful precisely because it leaves things out. Twenty to forty well-chosen links with real descriptions beat two hundred scraped from a sitemap.</p>
          <p>Anything borderline goes in the Optional group, which the spec tells assistants they are free to skip when context is tight.</p>
        </div>
      </details>

      <details class="ltxg-q">
        <summary>How often should I update it?<span class="ltxg-sign"></span></summary>
        <div class="ltxg-qa">
          <p>Whenever your services or main pages change — for most sites that is a few times a year. Set a reminder if you rearrange things often.</p>
          <p>A file pointing at URLs that 404 is worse than no file at all, because it wastes the one request an agent was ever going to make.</p>
        </div>
      </details>

      <details class="ltxg-q">
        <summary>Is llms.txt an official web standard?<span class="ltxg-sign"></span></summary>
        <div class="ltxg-qa">
          <p>No. It is a community proposal with no backing from the W3C, the IETF or any standards body, and nothing enforces it. Adoption is voluntary and uneven.</p>
          <p>Worth doing because it is cheap and might pay off. Not worth doing because someone told you it was required.</p>
        </div>
      </details>

    </div>
  </div>


  <!-- ========== CTA ========== -->
  <div class="ltxg-band">
    <div class="ltxg-cta">
      <div>
        <h3>Want this handled for you?</h3>
        <p>PressCare+ will write your llms.txt, put it in the right place on the server, and keep it current as your site changes — alongside the backups, updates and security work in every care plan.</p>
      </div>
      <a class="ltxg-ctabtn" href="https://presscare.com/">See care plans</a>
    </div>
  </div>

  <div class="ltxg-toast" id="ltxg-toast" role="status"></div>
</div>

<script>
(function(){
  var root = document.getElementById('ltxg-app');
  if(!root || root.dataset.ltxgReady === '1') return;
  root.dataset.ltxgReady = '1';

  var uid = 0;
  function id(){ uid += 1; return 'n' + uid; }

  var state = {
    name:'', url:'', summary:'', notes:'',
    sections:[
      { key:id(), title:'Services', optional:false, links:[ blankLink(), blankLink() ] },
      { key:id(), title:'Guides',   optional:false, links:[ blankLink() ] }
    ]
  };

  function blankLink(){ return { key:id(), title:'', url:'', desc:'' }; }

  var secWrap  = root.querySelector('#ltxg-sections');
  var preview  = root.querySelector('#ltxg-preview');
  var metaEl   = root.querySelector('#ltxg-meta');
  var checksEl = root.querySelector('#ltxg-checks');
  var toastEl  = root.querySelector('#ltxg-toast');

  /* ---------- helpers ---------- */

  function esc(s){
    return String(s).replace(/[&<>"']/g, function(c){
      return {'&':'&','<':'<','>':'>','"':'"',"'":'''}[c];
    });
  }

  function clean(s){ return String(s == null ? '' : s).replace(/\s+/g,' ').trim(); }

  function safeLabel(s){ return clean(s).replace(/[\[\]]/g,''); }
  function safeUrl(s){ return clean(s).replace(/[()\s]/g,''); }

  function absolute(u){
    u = clean(u);
    if(!u) return '';
    if(/^https?:\/\//i.test(u)) return u;
    if(/^\/\//.test(u)) return 'https:' + u;
    var base = clean(state.url).replace(/\/+$/,'');
    if(base && u.charAt(0) === '/') return base + u;
    if(base && !/^[a-z][a-z0-9+.-]*:/i.test(u)) return base + '/' + u.replace(/^\/+/,'');
    return u;
  }

  function titleFromUrl(u){
    var path = clean(u).replace(/^https?:\/\/[^\/]+/i,'').replace(/[?#].*$/,'').replace(/\/+$/,'');
    var seg  = path.split('/').filter(Boolean).pop() || '';
    seg = seg.replace(/\.(html?|php|md)$/i,'').replace(/[-_]+/g,' ').trim();
    if(!seg) return 'Home';
    return seg.charAt(0).toUpperCase() + seg.slice(1);
  }

  function findSection(key){
    for(var i=0;i<state.sections.length;i++){
      if(state.sections[i].key === key) return state.sections[i];
    }
    return null;
  }

  function orderedSections(){
    var normal = [], optional = [];
    state.sections.forEach(function(s){ (s.optional ? optional : normal).push(s); });
    return normal.concat(optional);
  }

  function toast(msg){
    toastEl.textContent = msg;
    toastEl.classList.add('on');
    clearTimeout(toastEl._t);
    toastEl._t = setTimeout(function(){ toastEl.classList.remove('on'); }, 1900);
  }

  /* ---------- build the file ---------- */

  function buildFile(){
    var out = [];
    out.push('# ' + (clean(state.name) || 'Your site name'));

    var sum = clean(state.summary);
    if(sum) out.push('', '> ' + sum);

    var notes = String(state.notes || '').trim();
    if(notes){
      out.push('');
      notes.split(/\n{2,}/).forEach(function(para, i){
        if(i) out.push('');
        out.push(para.split('\n').map(function(l){ return l.trim(); }).join(' ').trim());
      });
    }

    orderedSections().forEach(function(sec){
      var rows = sec.links.filter(function(l){ return clean(l.url) || clean(l.title); });
      if(!rows.length) return;

      out.push('', '## ' + (safeLabel(sec.title) || 'Links'), '');

      rows.forEach(function(l){
        var u     = absolute(safeUrl(l.url));
        var label = safeLabel(l.title) || (u ? titleFromUrl(u) : 'Untitled');
        var d     = clean(l.desc);
        out.push('- [' + label + '](' + (u || '#') + ')' + (d ? ': ' + d : ''));
      });
    });

    return out.join('\n').replace(/\n{3,}/g,'\n\n').replace(/\s+$/,'') + '\n';
  }

  /* ---------- checks ---------- */

  function runChecks(){
    var items = [];
    var urls = [], dupes = {};
    var linkCount = 0, relative = 0, insecure = 0, untitled = 0, emptyGroups = 0;

    orderedSections().forEach(function(sec){
      var rows = sec.links.filter(function(l){ return clean(l.url) || clean(l.title); });
      if(!rows.length){ emptyGroups++; return; }
      rows.forEach(function(l){
        linkCount++;
        var raw = safeUrl(l.url);
        var u   = absolute(raw);
        if(!raw) relative++;
        else if(!/^https?:\/\//i.test(u)) relative++;
        else if(/^http:\/\//i.test(u)) insecure++;
        if(!clean(l.title)) untitled++;
        if(u){
          var norm = u.toLowerCase().replace(/\/+$/,'');
          if(urls.indexOf(norm) > -1) dupes[norm] = true; else urls.push(norm);
        }
      });
    });

    if(!clean(state.name)){
      items.push(['bad','The H1 name is missing. The spec requires it — the file is not valid without it.']);
    } else {
      items.push(['ok','Name set. The file opens with a valid H1.']);
    }

    if(!clean(state.summary)){
      items.push(['warn','No summary line. Assistants lean on it hardest — add one sentence.']);
    } else if(clean(state.summary).length > 200){
      items.push(['warn','Summary is long. Trim it to one sentence an assistant can quote.']);
    } else {
      items.push(['ok','Summary reads as a blockquote directly under the name.']);
    }

    if(!linkCount){
      items.push(['warn','No links yet. A name and summary are valid, but the file earns its keep by pointing at pages.']);
    } else {
      var groups = orderedSections().filter(function(s){
        return s.links.some(function(l){ return clean(l.url) || clean(l.title); });
      }).length;
      items.push(['ok',linkCount + ' link' + (linkCount===1?'':'s') + ' across ' + groups + ' group' + (groups===1?'':'s') + '.']);
    }

    if(linkCount > 60){
      items.push(['warn','Over 60 links. Move the less important ones into the Optional group so assistants can skip them.']);
    }
    if(relative){
      items.push(['bad',relative + ' link' + (relative===1?' has':'s have') + ' no full URL. Set the home page URL above, or paste absolute links.']);
    }
    if(insecure){
      items.push(['warn',insecure + ' link' + (insecure===1?' uses':'s use') + ' http. Switch to https.']);
    }
    if(untitled){
      items.push(['warn',untitled + ' link' + (untitled===1?' has':'s have') + ' no title. The slug is being used instead — write something clearer.']);
    }
    var dupeCount = Object.keys(dupes).length;
    if(dupeCount){
      items.push(['warn',dupeCount + ' URL' + (dupeCount===1?' appears':'s appear') + ' more than once.']);
    }
    if(emptyGroups){
      items.push(['warn',emptyGroups + ' group' + (emptyGroups===1?' has':'s have') + ' no links and will be left out of the file.']);
    }
    if(state.sections.some(function(s){ return s.optional; })){
      items.push(['ok','Optional group is placed last, where the spec expects it.']);
    }

    checksEl.innerHTML = items.map(function(it){
      var cls = it[0] === 'ok' ? '' : it[0];
      return '<li class="' + cls + '"><span class="ltxg-flag ' + cls + '"></span><span>' + esc(it[1]) + '</span></li>';
    }).join('');
  }

  /* ---------- render ---------- */

  function renderSections(){
    secWrap.innerHTML = state.sections.map(function(sec){
      var rows = sec.links.map(function(l){
        return '' +
        '<div class="ltxg-link" data-link="'+sec.key+'|'+l.key+'">' +
          '<div><label class="ltxg-lab" for="t-'+l.key+'">Title</label>' +
            '<input type="text" id="t-'+l.key+'" data-f="title" value="'+esc(l.title)+'" placeholder="Care plans"></div>' +
          '<div><label class="ltxg-lab" for="u-'+l.key+'">URL</label>' +
            '<input type="text" id="u-'+l.key+'" data-f="url" value="'+esc(l.url)+'" placeholder="/wordpress-care-plans"></div>' +
          '<div><label class="ltxg-lab" for="d-'+l.key+'">Description</label>' +
            '<input type="text" id="d-'+l.key+'" data-f="desc" value="'+esc(l.desc)+'" placeholder="Monthly maintenance and backups"></div>' +
          '<button type="button" class="ltxg-x" data-act="del-link" title="Remove this link" aria-label="Remove this link">×</button>' +
        '</div>';
      }).join('');

      return '' +
      '<div class="ltxg-sec" data-sec="'+sec.key+'" data-optional="'+(sec.optional?1:0)+'">' +
        '<div class="ltxg-sechead">' +
          '<div class="ltxg-field">' +
            '<label class="ltxg-lab" for="s-'+sec.key+'">Group heading</label>' +
            '<input type="text" id="s-'+sec.key+'" data-f="sectitle" value="'+esc(sec.title)+'" placeholder="Services"'+(sec.optional?' readonly':'')+'>' +
          '</div>' +
          '<button type="button" class="ltxg-x" data-act="del-section" title="Remove this group" aria-label="Remove this group">×</button>' +
        '</div>' +
        (sec.optional ? '<p class="ltxg-optnote">Anything in here is marked as skippable. Assistants working with limited context drop this group first — use it for terms, archives and press pages.</p>' : '') +
        rows +
        '<div class="ltxg-rowbtns"><button type="button" class="ltxg-btn-3" data-act="add-link">Add a link</button></div>' +
      '</div>';
    }).join('');
  }

  function renderOutput(){
    var text = buildFile();
    preview.textContent = text;
    var lines = text.replace(/\n$/,'').split('\n').length;
    var bytes = (typeof Blob !== 'undefined') ? new Blob([text]).size : text.length;
    metaEl.innerHTML = '<span>' + lines + ' lines</span><span>·</span><span>' + bytes + ' bytes</span>';
    runChecks();
    return text;
  }

  function renderAll(){ renderSections(); renderOutput(); }

  /* ---------- events ---------- */

  root.addEventListener('input', function(e){
    var t = e.target;

    if(t.dataset && t.dataset.k){
      state[t.dataset.k] = t.value;
      renderOutput();
      return;
    }

    var f = t.dataset && t.dataset.f;
    if(!f) return;

    if(f === 'sectitle'){
      var secBox = t.closest('.ltxg-sec');
      var sec = secBox && findSection(secBox.dataset.sec);
      if(sec && !sec.optional){ sec.title = t.value; renderOutput(); }
      return;
    }

    var row = t.closest('.ltxg-link');
    if(!row) return;
    var parts = row.dataset.link.split('|');
    var s = findSection(parts[0]);
    if(!s) return;
    for(var i=0;i<s.links.length;i++){
      if(s.links[i].key === parts[1]){ s.links[i][f] = t.value; break; }
    }
    renderOutput();
  });

  root.addEventListener('click', function(e){
    var btn = e.target.closest('button[data-act]');
    if(!btn) return;
    var act = btn.dataset.act;

    if(act === 'add-section'){
      var n = state.sections.filter(function(s){ return !s.optional; }).length + 1;
      state.sections.push({ key:id(), title:'Group ' + n, optional:false, links:[ blankLink() ] });
      renderAll();
      return;
    }

    if(act === 'add-optional'){
      if(state.sections.some(function(s){ return s.optional; })){
        toast('You already have an Optional group');
        return;
      }
      state.sections.push({ key:id(), title:'Optional', optional:true, links:[ blankLink() ] });
      renderAll();
      return;
    }

    if(act === 'del-section'){
      var box = btn.closest('.ltxg-sec');
      state.sections = state.sections.filter(function(s){ return s.key !== box.dataset.sec; });
      if(!state.sections.length){
        state.sections.push({ key:id(), title:'Services', optional:false, links:[ blankLink() ] });
      }
      renderAll();
      return;
    }

    if(act === 'add-link'){
      var sbox = btn.closest('.ltxg-sec');
      var sec  = findSection(sbox.dataset.sec);
      if(sec){ sec.links.push(blankLink()); renderAll(); }
      return;
    }

    if(act === 'del-link'){
      var row = btn.closest('.ltxg-link');
      var p   = row.dataset.link.split('|');
      var s2  = findSection(p[0]);
      if(s2){
        s2.links = s2.links.filter(function(l){ return l.key !== p[1]; });
        if(!s2.links.length) s2.links.push(blankLink());
        renderAll();
      }
      return;
    }

    if(act === 'bulk'){ doBulk(); return; }
    if(act === 'copy'){ doCopy(); return; }
    if(act === 'download'){ doDownload(); return; }
  });

  /* ---------- bulk paste ---------- */

  function doBulk(){
    var box   = root.querySelector('#ltxg-bulk');
    var lines = box.value.split('\n').map(function(l){ return l.trim(); }).filter(Boolean);
    if(!lines.length){ toast('Paste some URLs first'); return; }

    var target = null;
    for(var i = state.sections.length - 1; i >= 0; i--){
      if(!state.sections[i].optional){ target = state.sections[i]; break; }
    }
    if(!target){
      target = { key:id(), title:'Pages', optional:false, links:[] };
      state.sections.push(target);
    }

    var added = 0;
    lines.forEach(function(line){
      var t = '', u = '', d = '';

      var md = line.match(/^[-*]?\s*\[([^\]]*)\]\(([^)]+)\)\s*:?\s*(.*)$/);
      if(md){
        t = md[1]; u = md[2]; d = md[3];
      } else {
        var bits = line.split('|').map(function(b){ return b.trim(); });
        u = bits[0] || '';
        t = bits[1] || '';
        d = bits[2] || '';
      }

      u = u.replace(/^[-*]\s*/,'');
      if(!u) return;
      if(!t) t = titleFromUrl(u);

      target.links.push({ key:id(), title:t, url:u, desc:d });
      added++;
    });

    target.links = target.links.filter(function(l){
      var empty = !clean(l.url) && !clean(l.title) && !clean(l.desc);
      return !(empty && target.links.length > 1);
    });

    box.value = '';
    renderAll();
    toast(added ? added + ' link' + (added===1?'':'s') + ' added to ' + (target.title || 'the group') : 'Nothing usable in that paste');
  }

  /* ---------- copy / download ---------- */

  function doCopy(){
    var text = buildFile();
    function fallback(){
      var ta = document.createElement('textarea');
      ta.value = text;
      ta.setAttribute('readonly','');
      ta.style.position = 'fixed';
      ta.style.left = '-9999px';
      document.body.appendChild(ta);
      ta.select();
      try{ document.execCommand('copy'); toast('Copied'); }
      catch(err){ toast('Select the file text and copy it manually'); }
      document.body.removeChild(ta);
    }
    if(navigator.clipboard && navigator.clipboard.writeText){
      navigator.clipboard.writeText(text).then(function(){ toast('Copied'); }, fallback);
    } else {
      fallback();
    }
  }

  function doDownload(){
    if(!clean(state.name)){ toast('Add a site name first'); return; }
    var text = buildFile();
    try{
      var blob = new Blob([text], { type:'text/plain;charset=utf-8' });
      var a = document.createElement('a');
      a.href = URL.createObjectURL(blob);
      a.download = 'llms.txt';
      document.body.appendChild(a);
      a.click();
      setTimeout(function(){ URL.revokeObjectURL(a.href); document.body.removeChild(a); }, 400);
      toast('Downloaded llms.txt');
    } catch(err){
      toast('Your browser blocked the download — use Copy instead');
    }
  }

  renderAll();
})();
</script>