Compare commits
11 Commits
1b75c799e5
...
daemon
| Author | SHA1 | Date | |
|---|---|---|---|
| 34733206c2 | |||
| 052723bdbc | |||
| 3a06ccbb91 | |||
| 0d47741293 | |||
| 072f70c7aa | |||
| b6223caa80 | |||
| d7fd4cd226 | |||
| 4392e811e6 | |||
| 338f489582 | |||
| 3014f18e16 | |||
| 49f326b5ae |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
.venv
|
.venv
|
||||||
|
__pycache__
|
||||||
bleh
|
bleh
|
||||||
|
|||||||
29
Dockerfile
Normal file
29
Dockerfile
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
FROM python:3.14-slim
|
||||||
|
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||||
|
PYTHONUNBUFFERED=1 \
|
||||||
|
PIP_NO_CACHE_DIR=1
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Optional but useful for Pillow runtime compatibility in slim images
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
libjpeg62-turbo \
|
||||||
|
zlib1g \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY requirements.txt ./
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Non-root runtime user
|
||||||
|
RUN useradd -m -u 10001 catnote && chown -R catnote:catnote /app
|
||||||
|
USER catnote
|
||||||
|
|
||||||
|
EXPOSE 5000
|
||||||
|
|
||||||
|
# BLEHD_SOCKET can be overridden at runtime
|
||||||
|
ENV BLEHD_SOCKET=/run/bleh/blehd.sock
|
||||||
|
|
||||||
|
CMD ["python", "app.py", "--port", "5000"]
|
||||||
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
CatNote is a minimalist web application that lets you generate Markdown-based receipts or notes, render them as PNGs, and send them directly to an MXW01 Cat Thermal Printer. Ideal for making quick, beautiful, and portable printed notes using Markdown, with optional image upload.
|
CatNote is a minimalist web application that lets you generate Markdown-based receipts or notes, render them as PNGs, and send them directly to an MXW01 Cat Thermal Printer. Ideal for making quick, beautiful, and portable printed notes using Markdown, with optional image upload.
|
||||||
|
|
||||||
**Note:** The default UI is currently *only available in Spanish*, as this project began for my own use. Support for both English and Spanish is planned for a future release.
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* Enter Markdown and preview output.
|
* Enter Markdown and preview output.
|
||||||
@@ -16,6 +14,9 @@ CatNote is a minimalist web application that lets you generate Markdown-based re
|
|||||||
|
|
||||||
* Python 3.8+
|
* Python 3.8+
|
||||||
* Flask
|
* Flask
|
||||||
|
* Requests
|
||||||
|
* Markdown
|
||||||
|
* JSON
|
||||||
* Pillow (PIL)
|
* Pillow (PIL)
|
||||||
* `bleh` v1.0.0 or later (see below)
|
* `bleh` v1.0.0 or later (see below)
|
||||||
|
|
||||||
@@ -42,7 +43,7 @@ CatNote is a minimalist web application that lets you generate Markdown-based re
|
|||||||
It's easiest to just get the latest release via wget:
|
It's easiest to just get the latest release via wget:
|
||||||
|
|
||||||
```
|
```
|
||||||
wget -O ./bleh https://git.netpaws.cc/igna/bleh/releases/download/latest/bleh
|
wget -O ./bleh https://git.netpaws.cc/igna/bleh/releases/download/latest/bleh_amd64
|
||||||
```
|
```
|
||||||
|
|
||||||
Place the built `bleh` binary in the app root directory (next to `app.py`).
|
Place the built `bleh` binary in the app root directory (next to `app.py`).
|
||||||
|
|||||||
84
static/translations.json
Normal file
84
static/translations.json
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
{
|
||||||
|
"en": {
|
||||||
|
"catnote": "CatNote",
|
||||||
|
"enter_markdown_here": "Enter Markdown here...",
|
||||||
|
"upload_image_optional": "Upload image (optional):",
|
||||||
|
"banner_mode_vertical": "Banner Mode (vertical)",
|
||||||
|
"dithering": "Dithering:",
|
||||||
|
"intensity": "Intensity:",
|
||||||
|
"rotate_image": "Rotate image:",
|
||||||
|
"print_mode": "Print mode:",
|
||||||
|
"printing": "Printing...",
|
||||||
|
"sent_to_printer": "Sent to printer",
|
||||||
|
"preview": "Preview",
|
||||||
|
"your_preview_will_appear_here": "Your preview will appear here",
|
||||||
|
"quick_markdown_reference": "Quick Markdown Cheatsheet",
|
||||||
|
"bold": "Bold:",
|
||||||
|
"italic": "Italic:",
|
||||||
|
"bold_and_italic": "Bold and Italic:",
|
||||||
|
"large_header": "Large Header:",
|
||||||
|
"medium_header": "Medium Header:",
|
||||||
|
"small_header": "Small Header:",
|
||||||
|
"bullet_list": "Bullet List:",
|
||||||
|
"numbered_list": "Numbered List:",
|
||||||
|
"image": "Image:",
|
||||||
|
"line_break": "Line break:",
|
||||||
|
"uploaded_image": "Uploaded image:",
|
||||||
|
"generate_btn": "🖼️ Generate",
|
||||||
|
"print_btn": "🖨️ Print",
|
||||||
|
"text": "text",
|
||||||
|
"title": "Title",
|
||||||
|
"element": "Element",
|
||||||
|
"alt_text": "Alt text",
|
||||||
|
"image_url": "Image URL",
|
||||||
|
"leave_an_empty_line": "Leave an empty line",
|
||||||
|
"uses_the_uploaded_image": "(uses the uploaded image)",
|
||||||
|
"no_dithering": "No dithering",
|
||||||
|
"font_size": "Font size:",
|
||||||
|
"font_small": "Small",
|
||||||
|
"font_normal": "Normal",
|
||||||
|
"font_large": "Large",
|
||||||
|
"font_xlarge": "Extra Large"
|
||||||
|
},
|
||||||
|
"es": {
|
||||||
|
"catnote": "CatNote",
|
||||||
|
"enter_markdown_here": "Ingrese Markdown aquí...",
|
||||||
|
"upload_image_optional": "Subir imagen (opcional):",
|
||||||
|
"banner_mode_vertical": "Modo Banner (vertical)",
|
||||||
|
"dithering": "Dithering:",
|
||||||
|
"intensity": "Intensidad:",
|
||||||
|
"rotate_image": "Rotar imagen:",
|
||||||
|
"print_mode": "Modo de impresión:",
|
||||||
|
"printing": "Imprimiendo...",
|
||||||
|
"sent_to_printer": "Enviado a impresora",
|
||||||
|
"preview": "Vista previa",
|
||||||
|
"your_preview_will_appear_here": "Su vista previa aparecerá aquí",
|
||||||
|
"quick_markdown_reference": "Referencia rápida de Markdown",
|
||||||
|
"bold": "Negrita:",
|
||||||
|
"italic": "Cursiva:",
|
||||||
|
"bold_and_italic": "Negrita y cursiva:",
|
||||||
|
"large_header": "Encabezado grande:",
|
||||||
|
"medium_header": "Encabezado mediano:",
|
||||||
|
"small_header": "Encabezado chico:",
|
||||||
|
"bullet_list": "Lista con viñetas:",
|
||||||
|
"numbered_list": "Lista numerada:",
|
||||||
|
"image": "Imágen:",
|
||||||
|
"line_break": "Salto de línea:",
|
||||||
|
"uploaded_image": "Imagen subida:",
|
||||||
|
"generate_btn": "🖼️ Generar",
|
||||||
|
"print_btn": "🖨️ Imprimir",
|
||||||
|
"text": "texto",
|
||||||
|
"title": "Título",
|
||||||
|
"element": "Elemento",
|
||||||
|
"alt_text": "Texto alternativo",
|
||||||
|
"image_url": "URL de la imagen",
|
||||||
|
"leave_an_empty_line": "Deje una línea vacía",
|
||||||
|
"uses_the_uploaded_image": "(usa la imagen cargada)",
|
||||||
|
"no_dithering": "Sin dithering",
|
||||||
|
"font_size": "Tamaño de fuente:",
|
||||||
|
"font_small": "Pequeño",
|
||||||
|
"font_normal": "Normal",
|
||||||
|
"font_large": "Grande",
|
||||||
|
"font_xlarge": "Extra grande"
|
||||||
|
}
|
||||||
|
}
|
||||||
654
templates/index.html
Normal file
654
templates/index.html
Normal file
@@ -0,0 +1,654 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>{{ t['catnote'] }}</title>
|
||||||
|
<link rel="manifest" href="/static/manifest.json">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
background: #181c1f;
|
||||||
|
color: #ddd;
|
||||||
|
min-height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.centered-flex {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
height: 100vh;
|
||||||
|
gap: 2.6em;
|
||||||
|
padding-top: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-card {
|
||||||
|
background: #22282c;
|
||||||
|
padding: 2em 2em 1em 2em;
|
||||||
|
border-radius: 1.5em;
|
||||||
|
box-shadow: 0 0 12px 0 #000a;
|
||||||
|
min-width: 410px;
|
||||||
|
max-width: 450px;
|
||||||
|
margin-right: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-ref {
|
||||||
|
background: #22282c;
|
||||||
|
border-radius: 1.2em;
|
||||||
|
box-shadow: 0 0 12px 0 #000a;
|
||||||
|
padding: 2em 2em 1.5em 2em;
|
||||||
|
color: #b6c8e0;
|
||||||
|
font-size: 1.08rem;
|
||||||
|
font-family: inherit;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 410px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-ref h4 {
|
||||||
|
margin: 0 0 0.4em 0;
|
||||||
|
color: #88e0ff;
|
||||||
|
font-size: 1.12rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-ref ul {
|
||||||
|
margin: 0.6em 0 0.3em 0.1em;
|
||||||
|
padding-left: 1.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-ref li {
|
||||||
|
margin-bottom: 0.36em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-ref code {
|
||||||
|
background: #181c1f;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
padding: 0.12em 0.46em;
|
||||||
|
color: #aef6cb;
|
||||||
|
font-size: 0.98rem;
|
||||||
|
margin-left: 0.18em;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
height: 14em;
|
||||||
|
padding: 0.75em;
|
||||||
|
border-radius: 0.7em;
|
||||||
|
border: 1.5px solid #444;
|
||||||
|
background: #181c1f;
|
||||||
|
color: #eee;
|
||||||
|
font-size: 1.06rem;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
resize: vertical;
|
||||||
|
font-family: 'DejaVu Sans Mono', 'Fira Mono', 'monospace';
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-right: 0;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 1em;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
button[type=submit] {
|
||||||
|
background: linear-gradient(90deg, #8ee3c1, #35a7ff);
|
||||||
|
color: #222;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.12rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.7em;
|
||||||
|
padding: 0.5em 1.6em;
|
||||||
|
margin-top: 0.1em;
|
||||||
|
box-shadow: 0 2px 12px #1116;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: filter 0.2s, box-shadow 0.2s;
|
||||||
|
filter: brightness(1);
|
||||||
|
min-width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button[type=submit]:hover {
|
||||||
|
filter: brightness(1.12);
|
||||||
|
box-shadow: 0 4px 18px #2229;
|
||||||
|
}
|
||||||
|
|
||||||
|
button[type=submit][name=print] {
|
||||||
|
background: linear-gradient(90deg, #ffeb3b, #ff9100);
|
||||||
|
color: #181c1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: #23282d;
|
||||||
|
padding: 2em 1.4em 1em 1.4em;
|
||||||
|
border-radius: 1.5em;
|
||||||
|
box-shadow: 0 0 12px 0 #0007;
|
||||||
|
min-width: 400px;
|
||||||
|
text-align: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-left: 0;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
margin-top: 1em;
|
||||||
|
max-width: 100%;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 0 8px 1px #111a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-msg {
|
||||||
|
margin: 0.6em 0 0.1em 0;
|
||||||
|
font-size: 1.12rem;
|
||||||
|
border-radius: 0.7em;
|
||||||
|
background: #294b3a;
|
||||||
|
color: #9fffc6;
|
||||||
|
padding: 0.55em 1.2em;
|
||||||
|
font-weight: 600;
|
||||||
|
box-shadow: 0 0 6px #183a24a0;
|
||||||
|
display: inline-block;
|
||||||
|
border-left: 4px solid #4ffab0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-err {
|
||||||
|
background: #48202c;
|
||||||
|
color: #ffdbe4;
|
||||||
|
border-left: 4px solid #ff6384;
|
||||||
|
box-shadow: 0 0 8px #22000690;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-size: 1em;
|
||||||
|
padding: 0 1em 0 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=file] {
|
||||||
|
margin: 0.5em 1em 1.5em 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=file]::file-selector-button {
|
||||||
|
background: linear-gradient(90deg, #8ee3c1, #35a7ff);
|
||||||
|
color: #222;
|
||||||
|
font-weight: bold;
|
||||||
|
border: none;
|
||||||
|
padding: 0.5em 1.2em;
|
||||||
|
border-radius: 0.7em;
|
||||||
|
box-shadow: 0 2px 12px #1116;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: filter 0.2s, box-shadow 0.2s;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=file]::file-selector-button:hover {
|
||||||
|
filter: brightness(1.12);
|
||||||
|
box-shadow: 0 4px 18px #2229;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-frame {
|
||||||
|
position: relative;
|
||||||
|
padding: 10px 0 10px 0;
|
||||||
|
margin-top: 1em;
|
||||||
|
overflow: hidden;
|
||||||
|
max-width: 424px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-frame::before,
|
||||||
|
.print-frame::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 10px;
|
||||||
|
background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZlcnNpb249IjEuMSIgdmlld0JveD0iMCAwIDQuMjMzMyA0LjIzMzMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIHRyYW5zZm9ybT0ibWF0cml4KC4wMzYxMzUgMCAwIC4wNDE3MjUgMS4xNTY4IDEuMTI4OSkiIGQ9Im04NS4xNDEgNzQuNDAzLTExNy4xNS0xZS02IDU4LjU3Ny0xMDEuNDZ6IiBmaWxsPSIjZmZmIiBzdHJva2UtbGluZWNhcD0ic3F1YXJlIiBzdHJva2Utb3BhY2l0eT0iLjk3NjQ3IiBzdHJva2Utd2lkdGg9Ii4yNjQ1OCIvPgo8L3N2Zz4K') repeat-x left;
|
||||||
|
background-size: 10px 384px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-frame::before {
|
||||||
|
top: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-frame::after {
|
||||||
|
bottom: 1px;
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-frame img {
|
||||||
|
background: #fff;
|
||||||
|
padding: 20px;
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
box-shadow: none;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 384px;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
font-size: 1.16em !important;
|
||||||
|
padding: 1em;
|
||||||
|
min-height: 12em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: left;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
background: #181c1f;
|
||||||
|
text-align: center;
|
||||||
|
text-align-last: center;
|
||||||
|
border: 1.5px solid #444;
|
||||||
|
color: #eee;
|
||||||
|
border-radius: 0.7em;
|
||||||
|
margin: 1em;
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
select option {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=number] {
|
||||||
|
background: #181c1f;
|
||||||
|
text-align: center;
|
||||||
|
border: 1.5px solid #444;
|
||||||
|
color: #eee;
|
||||||
|
border-radius: 0.7em;
|
||||||
|
margin: 1em;
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=number]::-webkit-inner-spin-button,
|
||||||
|
input[type=number]::-webkit-outer-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-dithering,
|
||||||
|
.options-intensity,
|
||||||
|
.options-rotation,
|
||||||
|
.options-printmode {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loading-box {
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#printing-text {
|
||||||
|
color: #eee;
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 18px;
|
||||||
|
background: #181c1f;
|
||||||
|
border: 1.5px solid #444;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 2px 12px #1116;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar-inner {
|
||||||
|
width: 40%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, #8ee3c1, #35a7ff);
|
||||||
|
border-radius: 12px;
|
||||||
|
position: absolute;
|
||||||
|
left: -40%;
|
||||||
|
animation: indeterminate-bar 1.2s infinite
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes indeterminate-bar {
|
||||||
|
0% {
|
||||||
|
left: 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
left: calc(100% - 40%);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
left: 0%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* this next bit is a mess but it works */
|
||||||
|
.options-bannermode {
|
||||||
|
display: flex;
|
||||||
|
align-items: left;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-left: 1em;
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-bannermode label {
|
||||||
|
font-size: 1em;
|
||||||
|
color: #eee;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-bannermode-inner {
|
||||||
|
display: block;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
padding-left: 35px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1em;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide the browser's default checkbox */
|
||||||
|
.options-bannermode-inner input {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
height: 0;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create a custom checkbox */
|
||||||
|
.checkmark {
|
||||||
|
position: absolute;
|
||||||
|
top: -25%;
|
||||||
|
left: 0;
|
||||||
|
height: 25px;
|
||||||
|
width: 25px;
|
||||||
|
background-color: #181c1f;
|
||||||
|
border-radius: 0.35em;
|
||||||
|
border: 1.5px solid #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* On mouse-over, add a grey background color */
|
||||||
|
.options-bannermode-inner:hover input~.checkmark {
|
||||||
|
filter: brightness(1.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* When the checkbox is checked, add a blue background */
|
||||||
|
.options-bannermode-inner input:checked~.checkmark {
|
||||||
|
background: linear-gradient(135deg, #8ee3c1, #35a7ff);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create the checkmark/indicator (hidden when not checked) */
|
||||||
|
.checkmark:after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show the checkmark when checked */
|
||||||
|
.options-bannermode-inner input:checked~.checkmark:after {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style the checkmark/indicator */
|
||||||
|
.options-bannermode-inner .checkmark:after {
|
||||||
|
left: 9px;
|
||||||
|
top: 5px;
|
||||||
|
width: 5px;
|
||||||
|
height: 10px;
|
||||||
|
border: solid white;
|
||||||
|
border-width: 0 3px 3px 0;
|
||||||
|
-webkit-transform: rotate(45deg);
|
||||||
|
-ms-transform: rotate(45deg);
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#lang-switcher {
|
||||||
|
position: fixed;
|
||||||
|
right: 24px;
|
||||||
|
bottom: 18px;
|
||||||
|
z-index: 1000;
|
||||||
|
background: #23282d;
|
||||||
|
border-radius: 0.7em;
|
||||||
|
box-shadow: 0 2px 12px #1116;
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
opacity: 0.95;
|
||||||
|
}
|
||||||
|
|
||||||
|
#lang-select {
|
||||||
|
background: #181c1f;
|
||||||
|
color: #eee;
|
||||||
|
border: 1.5px solid #444;
|
||||||
|
border-radius: 0.7em;
|
||||||
|
padding: 0.4em 1em;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1000px) {
|
||||||
|
body {
|
||||||
|
font-size: 1.28em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4 {
|
||||||
|
font-size: 2em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.centered-flex {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
height: auto;
|
||||||
|
gap: 1.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-card,
|
||||||
|
.preview-card,
|
||||||
|
.markdown-ref {
|
||||||
|
font-size: 1.18em !important;
|
||||||
|
min-width: auto;
|
||||||
|
max-width: 90vw;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0.8em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
font-size: 1.16em !important;
|
||||||
|
padding: 1em;
|
||||||
|
min-height: 12em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-msg {
|
||||||
|
font-size: 1.25em !important;
|
||||||
|
padding: 0.8em 1.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
button[type=submit] {
|
||||||
|
font-size: 1.3em !important;
|
||||||
|
padding: 0.9em 2.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=file] {
|
||||||
|
font-size: 1.16em !important;
|
||||||
|
padding: inherit 2.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-size: 1.16em !important;
|
||||||
|
padding: inherit 2.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
font-size: 1.16em !important;
|
||||||
|
padding: 0.6em 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=number] {
|
||||||
|
font-size: 1.16em !important;
|
||||||
|
padding: 0.6em 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* i'm so sorry, i'm not bothered to find out why or how this works */
|
||||||
|
.options-bannermode-inner .checkmark {
|
||||||
|
top: -10%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-bannermode-inner .checkmark::after {
|
||||||
|
left: 13px;
|
||||||
|
top: 7px;
|
||||||
|
transform: rotate(45deg) scale(1.66);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkmark {
|
||||||
|
width: 1.2em;
|
||||||
|
height: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-bannermode-inner {
|
||||||
|
padding-left: 55px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="centered-flex">
|
||||||
|
<div class="form-card">
|
||||||
|
<center>
|
||||||
|
<h2 style="margin-top:0.2em;">😺 {{ t['catnote'] }} 🖨️</h2>
|
||||||
|
</center>
|
||||||
|
<form method="post" enctype="multipart/form-data">
|
||||||
|
<textarea name="md" placeholder="{{ t['enter_markdown_here'] }}">{{ default_md }}</textarea><br>
|
||||||
|
<label for="userimg" style="display: inline-block; margin-bottom: 0.5em;">{{ t['upload_image_optional']
|
||||||
|
}}</label><br>
|
||||||
|
<input type="file" name="userimg" accept="image/png, image/jpeg"><br>
|
||||||
|
<div class="options">
|
||||||
|
<div class="options-bannermode">
|
||||||
|
<label class="options-bannermode-inner">
|
||||||
|
<input type="checkbox" name="bannermode" value="1" {% if current_bannermode %}checked{% endif %}>
|
||||||
|
<span class="checkmark"></span>
|
||||||
|
{{ t['banner_mode_vertical'] }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="options-dithering">
|
||||||
|
<label for="dithering">{{ t['dithering'] }}</label>
|
||||||
|
<select name="dithering">
|
||||||
|
{% for label, mode in dithering_modes.items() %}
|
||||||
|
<option value="{{ mode }}" {% if mode==current_dithering %}selected="selected" {% endif %}>{{ label }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="options-intensity">
|
||||||
|
<label for="intensity">{{ t['intensity'] }}</label>
|
||||||
|
<input type="number" name="intensity" value="{{ current_intensity }}" min="0" max="100" step="1">
|
||||||
|
</div>
|
||||||
|
<div class="options-rotation">
|
||||||
|
<label for="rotation">{{ t['rotate_image'] }}</label>
|
||||||
|
<select name="rotation">
|
||||||
|
<option value="0" {% if 0==current_rotation %}selected="selected" {% endif %}>0°</option>
|
||||||
|
<option value="90" {% if 90==current_rotation %}selected="selected" {% endif %}>90°</option>
|
||||||
|
<option value="180" {% if 180==current_rotation %}selected="selected" {% endif %}>180°</option>
|
||||||
|
<option value="270" {% if 270==current_rotation %}selected="selected" {% endif %}>270°</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="options-fontsize">
|
||||||
|
<label for="fontsize">{{ t['font_size'] }}</label>
|
||||||
|
<select name="fontsize">
|
||||||
|
<option value="small" {% if "small"==current_fontsize %}selected="selected" {% endif %}>{{ t['font_small'] }}</option>
|
||||||
|
<option value="normal" {% if "normal"==current_fontsize %}selected="selected" {% endif %}>{{ t['font_normal'] }}</option>
|
||||||
|
<option value="large" {% if "large"==current_fontsize %}selected="selected" {% endif %}>{{ t['font_large'] }}</option>
|
||||||
|
<option value="xlarge" {% if "xlarge"==current_fontsize %}selected="selected" {% endif %}>{{ t['font_xlarge'] }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="options-printmode">
|
||||||
|
<label for="printmode">{{ t['print_mode'] }}</label>
|
||||||
|
<select name="printmode">
|
||||||
|
<option value="1bpp" {% if "1bpp"==current_printmode %}selected="selected" {% endif %}>1-bit</option>
|
||||||
|
<option value="4bpp" {% if "4bpp"==current_printmode %}selected="selected" {% endif %}>4-bit</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div id="loading-box">
|
||||||
|
<div id="printing-text">{{ t['printing'] }}</div>
|
||||||
|
<div class="progress-bar">
|
||||||
|
<div class="progress-bar-inner"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="buttons">
|
||||||
|
<button type="submit" name="generate">{{ t['generate_btn'] }}</button>
|
||||||
|
<button type="submit" name="print">{{ t['print_btn'] }}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% if printed %}
|
||||||
|
<div class="status-msg">✅ {{ t['sent_to_printer'] }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if error %}
|
||||||
|
<div class="status-msg status-err">⚠️ {{ error }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="preview-card">
|
||||||
|
{% if img %}
|
||||||
|
<h2 style="margin-top:0.1em; margin-bottom:0;">{{ t['preview'] }}</h2>
|
||||||
|
<div class="print-frame">
|
||||||
|
<img src="data:image/png;base64,{{ img }}">
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<p style="opacity:.6;">{{ t['your_preview_will_appear_here'] }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="markdown-ref">
|
||||||
|
<h4>{{ t['quick_markdown_reference'] }}</h4>
|
||||||
|
<ul>
|
||||||
|
<li><b>{{ t['bold'] }}</b> <code>**{{ t['text'] }}**</code></li>
|
||||||
|
<li><b>{{ t['italic'] }}</b> <code>*{{ t['text'] }}*</code></li>
|
||||||
|
<li><b>{{ t['bold_and_italic'] }}</b> <code>***{{ t['text'] }}***</code></li>
|
||||||
|
<li><b>{{ t['large_header'] }}</b> <code># {{ t['title'] }}</code></li>
|
||||||
|
<li><b>{{ t['medium_header'] }}</b> <code>## {{ t['title'] }}</code></li>
|
||||||
|
<li><b>{{ t['small_header'] }}</b> <code>### {{ t['title'] }}</code></li>
|
||||||
|
<li><b>{{ t['bullet_list'] }}</b> <code>- {{ t['element'] }}</code></li>
|
||||||
|
<li><b>{{ t['numbered_list'] }}</b> <code>1. {{ t['element'] }}</code></li>
|
||||||
|
<li><b>{{ t['image'] }}</b> <code>![{{ t['alt_text'] }}]({{ t['image_url'] }})</code></li>
|
||||||
|
<li><b>{{ t['line_break'] }}</b> {{ t['Leave an empty line'] }}</li>
|
||||||
|
<li><b>{{ t['uploaded_image'] }}</b> <code>!(img)</code> {{ t['uses_the_uploaded_image'] }}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="lang-switcher">
|
||||||
|
<select id="lang-select">
|
||||||
|
<option value="es" {% if lang=='es' %}selected{% endif %}>Español</option>
|
||||||
|
<option value="en" {% if lang=='en' %}selected{% endif %}>English</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.getElementById('lang-select').addEventListener('change', function () {
|
||||||
|
window.location.search = '?lang=' + this.value;
|
||||||
|
});
|
||||||
|
document.querySelector('form').addEventListener('submit', function (e) {
|
||||||
|
if (document.activeElement && document.activeElement.name === "print") {
|
||||||
|
document.getElementById('loading-box').style.display = 'flex';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user