word generation

This commit is contained in:
Jonas Heinrich 2025-12-29 02:20:56 +01:00
parent c91e4de4a8
commit 5b8a8b7572
6 changed files with 428 additions and 235 deletions

BIN
assets/antrag_vorlage.docx Normal file

Binary file not shown.

View file

@ -31,7 +31,6 @@
jinja2 jinja2
google-generativeai # Dependency for Gemini API google-generativeai # Dependency for Gemini API
grpcio # Required by google-generativeai grpcio # Required by google-generativeai
reportlab # Dependency for PDF generation
python-docx # Dependency for Word document generation python-docx # Dependency for Word document generation
]; ];

View file

@ -13,11 +13,7 @@ from jinja2 import Environment, FileSystemLoader
import google.generativeai as genai import google.generativeai as genai
import re import re
from io import BytesIO from io import BytesIO
from reportlab.lib.pagesizes import A4 from datetime import datetime
from reportlab.lib.units import cm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
from reportlab.lib.enums import TA_LEFT, TA_JUSTIFY
try: try:
from docx import Document from docx import Document
from docx.shared import Pt, Inches from docx.shared import Pt, Inches
@ -84,8 +80,8 @@ class MeinAntragApp(BaseTemplateResource):
template = self.jinja_env.get_template('index.html') template = self.jinja_env.get_template('index.html')
resp.content_type = 'text/html; charset=utf-8' resp.content_type = 'text/html; charset=utf-8'
resp.text = template.render( resp.text = template.render(
meta_title='MeinAntrag Anfragelinks für FragDenStaat', meta_title='MeinAntrag Anträge an die Karlsruher Stadtverwaltung',
meta_description='Erstelle vorausgefüllte Anfragelinks für FragDenStaat.de, suche Behörden, füge Betreff und Text hinzu und teile den Link.', meta_description='Erstelle einfach Vorlagen für Anfragen oder Anträge an die Karlsruher Stadtverwaltung zu deinem persönlichen Thema und schicke diese direkt an eine Stadtratsfraktion!',
canonical_url=f"{SITE_BASE_URL}/" canonical_url=f"{SITE_BASE_URL}/"
) )
@ -301,188 +297,120 @@ WICHTIG:
'error': str(e) 'error': str(e)
}) })
class GeneratePDFResource:
def _generate_pdf(self, title, demand, justification, party_name=""):
"""Generate a PDF that looks like a city council proposal"""
buffer = BytesIO()
doc = SimpleDocTemplate(buffer, pagesize=A4,
rightMargin=2.5*cm, leftMargin=2.5*cm,
topMargin=2.5*cm, bottomMargin=2.5*cm)
# Container for the 'Flowable' objects
story = []
# Define styles
styles = getSampleStyleSheet()
# Custom styles for the document
title_style = ParagraphStyle(
'CustomTitle',
parent=styles['Heading1'],
fontSize=16,
textColor='black',
spaceAfter=30,
alignment=TA_LEFT,
fontName='Helvetica-Bold'
)
heading_style = ParagraphStyle(
'CustomHeading',
parent=styles['Heading2'],
fontSize=12,
textColor='black',
spaceAfter=12,
spaceBefore=20,
alignment=TA_LEFT,
fontName='Helvetica-Bold'
)
body_style = ParagraphStyle(
'CustomBody',
parent=styles['Normal'],
fontSize=11,
textColor='black',
spaceAfter=12,
alignment=TA_JUSTIFY,
fontName='Helvetica'
)
# Header with party name if provided
if party_name:
party_para = Paragraph(f"<b>Antrag der {party_name}</b>", body_style)
story.append(party_para)
story.append(Spacer(1, 0.5*cm))
# Title
if title:
title_para = Paragraph(f"<b>{title}</b>", title_style)
story.append(title_para)
# Demand section
if demand:
story.append(Spacer(1, 0.3*cm))
demand_heading = Paragraph("<b>Der Gemeinderat möge beschließen:</b>", heading_style)
story.append(demand_heading)
# Process demand text - replace newlines with proper breaks
demand_lines = demand.split('\n')
for line in demand_lines:
if line.strip():
demand_para = Paragraph(line.strip(), body_style)
story.append(demand_para)
# Justification section
if justification:
story.append(Spacer(1, 0.5*cm))
justification_heading = Paragraph("<b>Begründung/Sachverhalt</b>", heading_style)
story.append(justification_heading)
# Process justification text
justification_lines = justification.split('\n')
for line in justification_lines:
if line.strip():
justification_para = Paragraph(line.strip(), body_style)
story.append(justification_para)
# Build PDF
doc.build(story)
buffer.seek(0)
return buffer
def on_post(self, req, resp):
"""Generate PDF from form data"""
try:
# Get form data
title = req.get_param('title', default='') or ''
demand = req.get_param('demand', default='') or ''
justification = req.get_param('justification', default='') or ''
party_name = req.get_param('party_name', default='') or ''
# If empty, try to read from stream
if not title:
try:
stream = getattr(req, 'bounded_stream', req.stream)
raw_body = stream.read().decode('utf-8')
parsed = parse_qs(raw_body)
title = parsed.get('title', [''])[0]
demand = parsed.get('demand', [''])[0]
justification = parsed.get('justification', [''])[0]
party_name = parsed.get('party_name', [''])[0]
except Exception:
pass
# Generate PDF
pdf_buffer = self._generate_pdf(title, demand, justification, party_name)
# Return PDF
resp.content_type = 'application/pdf'
resp.set_header('Content-Disposition', 'inline; filename="antrag.pdf"')
resp.data = pdf_buffer.read()
except Exception as e:
import traceback
traceback.print_exc()
resp.status = falcon.HTTP_500
resp.content_type = 'application/json'
resp.text = json.dumps({
'success': False,
'error': str(e)
})
class GenerateWordResource: class GenerateWordResource:
def __init__(self):
# Get template path
script_dir = os.path.dirname(os.path.abspath(__file__))
self.template_path = os.path.join(script_dir, 'assets', 'antrag_vorlage.docx')
# Fallback if not in assets
if not os.path.exists(self.template_path):
assets_dir = os.path.join(script_dir, '..', 'assets')
self.template_path = os.path.join(assets_dir, 'antrag_vorlage.docx')
def _generate_word(self, title, demand, justification, party_name=""): def _generate_word(self, title, demand, justification, party_name=""):
"""Generate a Word document that looks like a city council proposal""" """Generate a Word document using the template"""
# Load template
if os.path.exists(self.template_path):
doc = Document(self.template_path)
else:
# Fallback: create new document if template not found
doc = Document() doc = Document()
# Set default font # Get current date in DD.MM.YYYY format
style = doc.styles['Normal'] current_date = datetime.now().strftime("%d.%m.%Y")
font = style.font
font.name = 'Arial'
font.size = Pt(11)
# Header with party name if provided # Combine demand for ANTRAGSTEXT (with heading)
if party_name: antragtext = "Der Gemeinderat möge beschließen:\n" + demand
party_para = doc.add_paragraph(f"Antrag der {party_name}")
party_para.runs[0].bold = True
party_para.runs[0].font.size = Pt(11)
doc.add_paragraph()
# Title # Replace placeholders in all paragraphs
if title: for paragraph in doc.paragraphs:
title_para = doc.add_paragraph(title) full_text = paragraph.text
title_para.runs[0].bold = True if not full_text:
title_para.runs[0].font.size = Pt(16) continue
title_para.paragraph_format.space_after = Pt(30)
# Demand section # Replace FRAKTION
if demand: if party_name and 'FRAKTION' in full_text:
doc.add_paragraph() for run in paragraph.runs:
demand_heading = doc.add_paragraph("Der Gemeinderat möge beschließen:") if 'FRAKTION' in run.text:
demand_heading.runs[0].bold = True run.text = run.text.replace('FRAKTION', party_name)
demand_heading.runs[0].font.size = Pt(12)
demand_heading.paragraph_format.space_before = Pt(20)
demand_heading.paragraph_format.space_after = Pt(12)
# Process demand text # Replace XX.XX.XXXX with current date
demand_lines = demand.split('\n') if 'XX.XX.XXXX' in full_text:
for line in demand_lines: for run in paragraph.runs:
if 'XX.XX.XXXX' in run.text:
run.text = run.text.replace('XX.XX.XXXX', current_date)
# Replace ANTRAGSTITEL (bold)
if 'ANTRAGSTITEL' in full_text:
paragraph.clear()
run = paragraph.add_run(title)
run.bold = True
# Replace ANTRAGSTEXT
if 'ANTRAGSTEXT' in full_text:
paragraph.clear()
lines = antragtext.split('\n')
for i, line in enumerate(lines):
if line.strip(): if line.strip():
doc.add_paragraph(line.strip()) run = paragraph.add_run(line.strip())
if i == 0: # First line (heading) should be bold
run.bold = True
if i < len(lines) - 1:
paragraph.add_run('\n')
# Justification section # Replace BEGRÜNDUNGSTEXT
if justification: if 'BEGRÜNDUNGSTEXT' in full_text:
doc.add_paragraph() paragraph.clear()
justification_heading = doc.add_paragraph("Begründung/Sachverhalt") lines = justification.split('\n')
justification_heading.runs[0].bold = True for i, line in enumerate(lines):
justification_heading.runs[0].font.size = Pt(12)
justification_heading.paragraph_format.space_before = Pt(20)
justification_heading.paragraph_format.space_after = Pt(12)
# Process justification text
justification_lines = justification.split('\n')
for line in justification_lines:
if line.strip(): if line.strip():
doc.add_paragraph(line.strip()) paragraph.add_run(line.strip())
if i < len(lines) - 1:
paragraph.add_run('\n')
# Also check tables for placeholders
for table in doc.tables:
for row in table.rows:
for cell in row.cells:
for paragraph in cell.paragraphs:
full_text = paragraph.text
if not full_text:
continue
if party_name and 'FRAKTION' in full_text:
for run in paragraph.runs:
if 'FRAKTION' in run.text:
run.text = run.text.replace('FRAKTION', party_name)
if 'XX.XX.XXXX' in full_text:
for run in paragraph.runs:
if 'XX.XX.XXXX' in run.text:
run.text = run.text.replace('XX.XX.XXXX', current_date)
if 'ANTRAGSTITEL' in full_text:
paragraph.clear()
run = paragraph.add_run(title)
run.bold = True
if 'ANTRAGSTEXT' in full_text:
paragraph.clear()
lines = antragtext.split('\n')
for i, line in enumerate(lines):
if line.strip():
run = paragraph.add_run(line.strip())
if i == 0:
run.bold = True
if i < len(lines) - 1:
paragraph.add_run('\n')
if 'BEGRÜNDUNGSTEXT' in full_text:
paragraph.clear()
lines = justification.split('\n')
for i, line in enumerate(lines):
if line.strip():
paragraph.add_run(line.strip())
if i < len(lines) - 1:
paragraph.add_run('\n')
# Save to buffer # Save to buffer
buffer = BytesIO() buffer = BytesIO()
@ -583,7 +511,6 @@ meinantrag = MeinAntragApp()
impressum = ImpressumResource() impressum = ImpressumResource()
datenschutz = DatenschutzResource() datenschutz = DatenschutzResource()
generate_antrag = GenerateAntragResource() generate_antrag = GenerateAntragResource()
generate_pdf = GeneratePDFResource()
generate_word = GenerateWordResource() generate_word = GenerateWordResource()
robots = RobotsResource() robots = RobotsResource()
sitemap = SitemapResource() sitemap = SitemapResource()
@ -592,7 +519,6 @@ app.add_route('/', meinantrag)
app.add_route('/impressum', impressum) app.add_route('/impressum', impressum)
app.add_route('/datenschutz', datenschutz) app.add_route('/datenschutz', datenschutz)
app.add_route('/api/generate-antrag', generate_antrag) app.add_route('/api/generate-antrag', generate_antrag)
app.add_route('/api/generate-pdf', generate_pdf)
app.add_route('/api/generate-word', generate_word) app.add_route('/api/generate-word', generate_word)
app.add_route('/robots.txt', robots) app.add_route('/robots.txt', robots)
app.add_route('/sitemap.xml', sitemap) app.add_route('/sitemap.xml', sitemap)

View file

@ -8,20 +8,20 @@
<link href="/static/css/select2.min.css" rel="stylesheet"> <link href="/static/css/select2.min.css" rel="stylesheet">
<link href="/static/css/select2-bootstrap-5-theme.min.css" rel="stylesheet"> <link href="/static/css/select2-bootstrap-5-theme.min.css" rel="stylesheet">
<meta name="description" content="{{ meta_description | default('Erstelle vorausgefüllte FragDenStaat.de-Anfragelinks und teile sie mit Freund:innen. Suche Behörden, füge Betreff und Text hinzu und generiere einen teilbaren Link.') }}"> <meta name="description" content="{{ meta_description | default('Erstelle einfach Vorlagen für Anfragen oder Anträge an die Karlsruher Stadtverwaltung zu deinem persönlichen Thema und schicke diese direkt an eine Stadtratsfraktion!') }}">
<link rel="canonical" href="{{ canonical_url | default('/') }}"> <link rel="canonical" href="{{ canonical_url | default('/') }}">
<link rel="alternate" hreflang="de" href="{{ canonical_url | default('/') }}"> <link rel="alternate" hreflang="de" href="{{ canonical_url | default('/') }}">
<meta property="og:type" content="website"> <meta property="og:type" content="website">
<meta property="og:site_name" content="MeinAntrag"> <meta property="og:site_name" content="MeinAntrag">
<meta property="og:title" content="{{ meta_title | default('MeinAntrag') }}"> <meta property="og:title" content="{{ meta_title | default('MeinAntrag') }}">
<meta property="og:description" content="{{ meta_description | default('Erstelle vorausgefüllte FragDenStaat.de-Anfragelinks und teile sie mit Freund:innen.') }}"> <meta property="og:description" content="{{ meta_description | default('Erstelle einfach Vorlagen für Anfragen oder Anträge an die Karlsruher Stadtverwaltung zu deinem persönlichen Thema und schicke diese direkt an eine Stadtratsfraktion!') }}">
<meta property="og:locale" content="de_DE"> <meta property="og:locale" content="de_DE">
<meta property="og:url" content="{{ canonical_url | default('/') }}"> <meta property="og:url" content="{{ canonical_url | default('/') }}">
<meta name="twitter:card" content="summary_large_image"> <meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ meta_title | default('MeinAntrag') }}"> <meta name="twitter:title" content="{{ meta_title | default('MeinAntrag') }}">
<meta name="twitter:description" content="{{ meta_description | default('Erstelle vorausgefüllte FragDenStaat.de-Anfragelinks und teile sie mit Freund:innen.') }}"> <meta name="twitter:description" content="{{ meta_description | default('Erstelle einfach Vorlagen für Anfragen oder Anträge an die Karlsruher Stadtverwaltung zu deinem persönlichen Thema und schicke diese direkt an eine Stadtratsfraktion!') }}">
{% if noindex %}<meta name="robots" content="noindex,follow">{% endif %} {% if noindex %}<meta name="robots" content="noindex,follow">{% endif %}
<meta name="theme-color" content="#667eea"> <meta name="theme-color" content="#667eea">

View file

@ -69,13 +69,6 @@
</svg> </svg>
<span id="mailBtnText">Mail an Fraktion senden</span> <span id="mailBtnText">Mail an Fraktion senden</span>
</button> </button>
<button type="button" class="btn btn-primary" id="pdfBtn">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-pdf me-2" viewBox="0 0 16 16">
<path d="M8.5 6a.5.5 0 0 0-1 0v1.5H6a.5.5 0 0 0 0 1h1.5V10a.5.5 0 0 0 1 0V8.5H10a.5.5 0 0 0 0-1H8.5z"/>
<path d="M14 14V4.5L9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2M9.5 3A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5z"/>
</svg>
PDF anzeigen
</button>
<button type="button" class="btn btn-primary" id="wordBtn"> <button type="button" class="btn btn-primary" id="wordBtn">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-earmark-word me-2" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-earmark-word me-2" viewBox="0 0 16 16">
<path d="M5.485 6.879a.5.5 0 1 0-.97.242l1.5 6a.5.5 0 0 0 .539.314l1.5-.5a.5.5 0 0 0 .186-.596l-.737-2.945 2.679-3.42a.5.5 0 1 0-.758-.652L6.978 8.616l-1.493-.5z"/> <path d="M5.485 6.879a.5.5 0 1 0-.97.242l1.5 6a.5.5 0 0 0 .539.314l1.5-.5a.5.5 0 0 0 .186-.596l-.737-2.945 2.679-3.42a.5.5 0 1 0-.758-.652L6.978 8.616l-1.493-.5z"/>
@ -217,46 +210,6 @@
} }
}); });
// Handle PDF button click
$('#pdfBtn').on('click', function() {
const title = $('#antragstitel').val() || '';
const demand = $('#forderung').val() || '';
const justification = $('#begruendung').val() || '';
const partyName = $('#resultFields').data('party-name') || '';
// Prepare form data
const formData = new URLSearchParams();
formData.append('title', title);
formData.append('demand', demand);
formData.append('justification', justification);
if (partyName) {
formData.append('party_name', partyName);
}
// Open PDF in new window
fetch('/api/generate-pdf', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: formData.toString()
})
.then(response => {
if (!response.ok) {
throw new Error('Fehler beim Generieren des PDFs');
}
return response.blob();
})
.then(blob => {
const url = window.URL.createObjectURL(blob);
window.open(url, '_blank');
})
.catch(error => {
console.error('Error:', error);
alert('Fehler beim Generieren des PDFs: ' + error.message);
});
});
// Handle Word button click // Handle Word button click
$('#wordBtn').on('click', function() { $('#wordBtn').on('click', function() {
const title = $('#antragstitel').val() || ''; const title = $('#antragstitel').val() || '';

315
vorlage.html Normal file
View file

@ -0,0 +1,315 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<link type="text/css" href="https://pages.github.com/css/pages.css" media="all" rel="stylesheet">
<!-- Begin Jekyll SEO tag v2.8.0 -->
<title>GitHub Pages | Websites for you and your projects, hosted directly from your GitHub repository. Just edit, push, and your changes are live.</title>
<meta name="generator" content="Jekyll v3.10.0" />
<meta property="og:title" content="GitHub Pages" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="Websites for you and your projects, hosted directly from your GitHub repository. Just edit, push, and your changes are live." />
<meta property="og:description" content="Websites for you and your projects, hosted directly from your GitHub repository. Just edit, push, and your changes are live." />
<link rel="canonical" href="https://pages.github.com/" />
<meta property="og:url" content="https://pages.github.com/" />
<meta property="og:site_name" content="GitHub Pages" />
<meta property="og:type" content="website" />
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="GitHub Pages" />
<meta name="twitter:site" content="@github" />
<script type="application/ld+json">
{"@context":"https://schema.org","@type":"WebSite","description":"Websites for you and your projects, hosted directly from your GitHub repository. Just edit, push, and your changes are live.","headline":"GitHub Pages","name":"GitHub Pages","url":"https://pages.github.com/"}</script>
<!-- End Jekyll SEO tag -->
</head>
<body class="home ">
<section id="hero-spot" class="hero-spot">
<a href="/"><img src="https://pages.github.com/images/logo.svg" alt="GitHub Pages" class="logo" /></a>
<h1>Websites for you and your projects.</h1>
<h2>Hosted directly from your <a href="https://github.com">GitHub repository</a>. Just edit, push, and your changes are live.</h2>
<a href="https://help.github.com/pages/" class="help-link">Pages Help</a>
<div id="slideshow">
<img src="https://pages.github.com/images/slideshow/bootstrap.png" alt="Bootstrap" class="slide active" width="893" />
</div>
</section>
<section id="tutorial" class="tutorial">
<h1>Ready to get started? Build your own site from scratch or generate one for your project.</h1>
<h2>You get one site per GitHub account and organization, <br />and unlimited project sites. Lets get started.</h2>
<ul class="tabs">
<li><a href="#user-site" class="selected">User or organization site</a></li>
<li><a href="#project-site">Project site</a></li>
</ul>
<!-- ### Start of tutorials -->
<ul id="user-site" class="tutorial-list wrapper active">
<li id="create-repo-step" class="image-right">
<h4>Create a repository</h4>
<p>Head over to <a href="https://github.com">GitHub</a> and <a data-proofer-ignore="true" href="https://github.com/new">create a new public repository</a> named <em>username</em>.github.io, where <em>username</em> is your username (or organization name) on GitHub.</p>
<p class="details">If the first part of the repository doesnt exactly match your username, it wont work, so make sure to get it right.</p>
</li>
<li class="question">
<h4>What git client are you using?</h4>
<ul class="tabs">
<li><a id="option-terminal" href="#terminal-step-1" class="selected">A terminal</a></li>
<li><a id="option-desktop" href="#setup-in-desktop">GitHub Desktop</a></li>
<li><a id="option-newuser" href="#new-user-step-1">I don't know</a></li>
</ul>
</li>
<li id="new-user-step-1" class="option-newuser">
<h4>Download GitHub Desktop</h4>
<p>GitHub Desktop is a great way to use Git and GitHub on macOS and Windows.</p>
<a class="desktop-download" href="https://desktop.github.com"><span class="icon"></span>Download GitHub Desktop</a>
<img src="https://pages.github.com/images/dashboard@2x.png" width="1054" alt="GitHub Desktop screenshot" class="full-size" />
</li>
<li id="terminal-step-1" class="option-terminal">
<h4>Clone the repository</h4>
<p>Go to the folder where you want to store your project, and clone the new repository:</p>
<div class="terminal">
<div class="header"></div>
<div class="shell">
<p><span class="path">~</span><span class="prompt">$</span>git clone https://github.com/<em>username</em>/<em>username</em>.github.io</p>
</div>
</div>
</li>
<li id="setup-in-desktop" class="option-desktop image-right">
<h4>Clone the repository</h4>
<p>Click the "Set up in Desktop" button. When the GitHub desktop app opens, save the project.</p>
<p class="details">If the app doesn't open, launch it and clone the repository from the app.</p>
</li>
<li id="setup-in-desktop" class="option-newuser image-right">
<h4>Clone the repository</h4>
<p>After finishing the installation, head back to GitHub.com and refresh the page. Click the "Set up in Desktop" button. When the GitHub desktop app opens, save the project.</p>
<p class="details">If the app doesn't open, launch it and clone the repository from the app.</p>
</li>
<li class="option-terminal">
<h4>Hello World</h4>
<p>Enter the project folder and add an index.html file:</p>
<div class="terminal">
<div class="header"></div>
<div class="shell">
<p><span class="path">~</span><span class="prompt">$</span>cd <em>username</em>.github.io</p>
<p><span class="path">~</span><span class="prompt">$</span>echo "Hello World" > index.html</p>
</div>
</div>
</li>
<li class="option-desktop option-newuser">
<h4>Create an index file</h4>
<p>Grab your favorite text editor and add an index.html file to your project:</p>
<div class="terminal">
<div class="header">index.html</div>
<code class="shell">
<pre>&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;body&gt;
&lt;h1&gt;Hello World&lt;/h1&gt;
&lt;p&gt;I'm hosted with GitHub Pages.&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
</code>
</li>
<li class="option-terminal">
<h4>Push it</h4>
<p>Add, commit, and push your changes:</p>
<div class="terminal">
<div class="header"></div>
<div class="shell">
<p><span class="path">~</span><span class="prompt">$</span>git add --all</p>
<p><span class="path">~</span><span class="prompt">$</span>git commit -m "Initial commit"</p>
<p><span class="path">~</span><span class="prompt">$</span>git push -u origin main</p>
</div>
</div>
</li>
<li class="option-desktop option-newuser">
<h4>Commit &amp; publish</h4>
<p>Enter the repository, commit your changes, and press the publish button.</p>
<img src="https://pages.github.com/images/desktop-demo@2x.gif" width="841" alt="Demonstration of steps required to create the initial commit and publish the repository in GitHub Desktop" class="macos-drop-shadow" />
</li>
<li class="option-all">
<h4>…and you're done!</h4>
<p>Fire up a browser and go to <strong>https://<em>username</em>.github.io</strong>.</p>
<div class="hero-octicon">
<span class="mega-octicon octicon-check"></span>
</div>
</li>
</ul>
<!-- End of user site tutorial -->
<!-- Project Site tutorial -->
<ul id="project-site" class="tutorial-list wrapper">
<li class="question">
<h4>Use a theme, or start from scratch?</h4>
<p>You have the option to start with one of the pre-built themes,
<br>or to create a site from scratch.
<ul class="tabs">
<li><a id="option-generate" href="#generate-step-1" class="selected">Choose a theme</a></li>
<li><a id="option-vanilla" href="#vanilla-step-1">Start from scratch</a></li>
</ul>
</li>
<li id="generate-step-1" class="option-generate">
<h4>Repository Settings</h4>
<p>Head over to <a href="https://github.com/">GitHub.com</a> and create a new repository, or go to an existing one.
<br><strong>Click on the Settings tab</strong>.</p>
<img src="https://pages.github.com/images/repo-settings@2x.png" width="720" alt="Settings for a repository" />
</li>
<li class="option-generate">
<h4>Theme chooser</h4>
<p>Scroll down to the <strong>GitHub Pages</strong> section. Press <strong>Choose a theme</strong>.</p>
<img src="https://pages.github.com/images/launch-theme-chooser@2x.png" width="720" alt="Automatic Generator button on GitHub.com, Settings" />
</li>
<li class="option-generate">
<h4>Pick a theme</h4>
<p>Choose one of the themes from the carousel at the top.
<br>When you're done, click <strong>Select theme</strong> on the right.</p>
<img src="https://pages.github.com/images/theme-chooser@2x.png" class="full-size" width="720" alt="Choose layout" />
</li>
<li class="option-generate">
<h4>Edit content</h4>
<p>Use the editor to add content to your site.</p>
<img class="full-size" src="https://pages.github.com/images/code-editor@2x.png" width="720" alt="Add content to your GitHub Pages site" />
</li>
<li class="option-generate">
<h4>Commit</h4>
<p>Enter a commit comment and click on <strong>Commit changes</strong> below the editor.</p>
<img class="full-size" src="https://pages.github.com/images/commit-edits@2x.png" width="720" alt="Commit Markdown content to your repository" />
</li>
<!-- Start of vanilla sub tutorial -->
<li id="vanilla-step-1" class="option-vanilla">
<h4>Create an index file</h4>
<p>Head over to <a href="https://github.com/">GitHub.com</a> and <a data-proofer-ignore="true" href="https://github.com/new">create a new repository</a>, or go to an existing one.
<br />Click on the <strong>Create new file</strong> button.</p>
<img src="https://pages.github.com/images/new-create-file@2x.png" width="720" alt="Create a file in your repository" />
</li>
<li class="option-vanilla">
<h4>Hello World</h4>
<p>Name the file <code>index.html</code> and type some HTML content into the editor.</p>
<img src="https://pages.github.com/images/new-index-html@2x.png" width="720" alt="Hello World on GitHub.com" />
</li>
<li class="option-vanilla">
<h4>Commit the file</h4>
<p>Scroll to the bottom of the page, write a commit message, and commit the new file.</p>
<img src="https://pages.github.com/images/new-commit-file@2x.png" width="720" alt="Commit the file" />
</li>
<li class="option-vanilla">
<h4>Repository Settings</h4>
<p><strong>Click on the Settings tab</strong> and scroll down to the GitHub Pages section.
<br />Then select the <strong>main branch</strong> source and click on the <strong>Save</strong> button.</p>
<img src="https://pages.github.com/images/source-setting@2x.png" width="720" alt="GitHub Pages Source Setting" />
</li>
<li class="option-all">
<h4>…and you're done!</h4>
<p>Fire up a browser and go to <strong>http://<em>username</em>.github.io/<em>repository</em></strong>.</p>
<div class="hero-octicon">
<span class="mega-octicon octicon-check"></span>
</div>
</li>
</ul>
</section>
<!-- End of tutorial section -->
<section id="next-steps">
<h1>Now that youre up and running, here are a few things you should know.</h1>
<ul class="next-steps wrapper">
<li class="jekyll">
<a class="hero-octicon" href="https://docs.github.com/en/pages/setting-up-a-github-pages-site-with-jekyll/about-github-pages-and-jekyll">
<span class="mega-octicon octicon-pencil"></span>
</a>
<h4><a href="https://docs.github.com/en/pages/setting-up-a-github-pages-site-with-jekyll/about-github-pages-and-jekyll">Blogging with Jekyll</a></h4>
<p>Using <a href="https://jekyllrb.com">Jekyll</a>, you can blog using beautiful Markdown syntax, and without having to deal with any databases. <a href="https://jekyllrb.com/docs/">Learn how to set up Jekyll</a>.</p>
</li>
<li class="custom-urls">
<a class="hero-octicon" href="https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site">
<span class="mega-octicon octicon-link"></span>
</a>
<h4><a href="https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site">Custom URLs</a></h4>
<p>Want to use your own custom domain for a GitHub Pages site? Just create a file named CNAME and include your URL. <a href="https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site">Read more</a>.</p>
</li>
<li class="guides">
<a class="hero-octicon" href="https://docs.github.com/pages">
<span class="mega-octicon octicon-book"></span>
</a>
<h4><a href="https://docs.github.com/pages">Guides</a></h4>
<p>Learn how to create custom 404 pages, use submodules, and <a href="https://docs.github.com/pages">learn more about GitHub Pages</a>.</p>
</li>
</ul>
</section>
<script src="https://pages.github.com/js/jquery.js"></script>
<script>window.slides = {"bootstrap":"Bootstrap","yeoman":"Yeoman","facebookdesign":"Facebook Design","foundation":"Foundation","ghtraining":"GitHub Training","adobeos":"Adobe Open Source","jekyllrb":"Jekyll","electron":"Electron","semanticui":"Semantic UI","microsoft":"Microsoft on GitHub"}</script>
<script src="https://pages.github.com/js/application.js"></script>
<footer class="page-footer">
<ul class="site-footer-links right">
<li><a href="https://www.githubstatus.com/">Status</a></li>
<li><a href="https://docs.github.com/rest">API</a></li>
<li><a href="https://training.github.com">Training</a></li>
<li><a href="https://shop.github.com">Shop</a></li>
<li><a href="https://github.blog">Blog</a></li>
<li><a href="https://github.com/about">About</a></li>
</ul>
<a href="/">
<span class="mega-octicon octicon-mark-github"></span>
</a>
<ul class="site-footer-links">
<li>&copy; 2025 <span>GitHub</span>, Inc.</li>
<li><a href="https://docs.github.com/en/github/site-policy/github-terms-of-service">Terms</a></li>
<li><a href="https://docs.github.com/en/github/site-policy/github-privacy-statement">Privacy</a></li>
<li><a href="https://github.com/security">Security</a></li>
<li><a href="https://support.github.com">Contact</a></li>
</ul>
</footer>
</body>
</html>