Merge pull request 'Dev - v1.1.0: Rename to Instameex, reorganize repo, redesign UI' (#1) from dev into main
Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
@@ -1,28 +1,32 @@
|
|||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<img src="src/assets/img/logo-long.svg" alt="Instameex logo" width="400"/>
|
||||||
|
</div>
|
||||||
|
<p/>
|
||||||
|
<div align="center">
|
||||||
|
<p>Mix your SDR and HDR exports into an Instagram-ready HDR photo.</p>
|
||||||
|
</div>
|
||||||
|
</p>
|
||||||
|
<div align="center">
|
||||||
|
<img src="illustration/instameex-illustration.png" alt="Instameex screenshot" width="640"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> ⚠️ _This is an heavily vibe-coded proof of concept — **do not expose it to the internet** — use it at your own risk._
|
> ⚠️ _This is an heavily vibe-coded proof of concept — **do not expose it to the internet** — use it at your own risk._
|
||||||
> _Github repo is a mirror of https://git.djeex.fr/Djeex/insta-hdr-converter. You'll find full package, history and release note there._
|
> _Github repo is a mirror of https://git.djeex.fr/Djeex/instameex. You'll find full package, history and release note there._
|
||||||
|
|
||||||
# Instagram HDR Assembler
|
# Instameex
|
||||||
|
|
||||||
A small web front end for assembling Instagram-compatible HDR JPEGs (gain
|
A small web front end for assembling Instagram-compatible HDR JPEGs (gain
|
||||||
map) from an SDR export and an HDR export out of Lightroom or Camera Raw.
|
map) from an SDR export and an HDR export out of Lightroom or Camera Raw.
|
||||||
|
|
||||||
The gain-map assembly logic ([app/assembler.py](app/assembler.py)) is
|
The gain-map assembly logic ([src/script/app/assembler.py](src/script/app/assembler.py)) is
|
||||||
adapted from [kostis-kounadis/instagram-hdr-assembler](https://github.com/kostis-kounadis/instagram-hdr-assembler),
|
adapted from [kostis-kounadis/instagram-hdr-assembler](https://github.com/kostis-kounadis/instagram-hdr-assembler),
|
||||||
itself based on the reverse-engineering work of
|
itself based on the reverse-engineering work of
|
||||||
[karachungen/instagram-hdr-converter](https://github.com/karachungen/instagram-hdr-converter).
|
[karachungen/instagram-hdr-converter](https://github.com/karachungen/instagram-hdr-converter).
|
||||||
Original MIT license kept in [LICENSE](LICENSE).
|
Original MIT license kept in [LICENSE](LICENSE).
|
||||||
|
|
||||||
Only the upstream's Method 2 (starting from an HDR JPEG that already
|
|
||||||
carries a gain map) is implemented here. Method 1's AVIF decoding and
|
|
||||||
its quality/transfer/gamut settings were dropped, but the HDR side can
|
|
||||||
still be a 32-bit float linear TIFF (Lightroom's HDR TIFF export)
|
|
||||||
instead of a gain-map JPEG for a more precise result. Either way the
|
|
||||||
gain map is recomputed rather than reused as-is (see below), since it
|
|
||||||
needs to be correct against the SDR file you actually provide.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
1. Upload an SDR file (`.jpg`/`.jpeg`) and an HDR file: either a gain-map
|
1. Upload an SDR file (`.jpg`/`.jpeg`) and an HDR file: either a gain-map
|
||||||
|
|||||||
+1
-3
@@ -27,9 +27,7 @@ COPY requirements.txt .
|
|||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
COPY main.py .
|
COPY main.py .
|
||||||
COPY app/ app/
|
COPY src/ src/
|
||||||
COPY templates/ templates/
|
|
||||||
COPY static/ static/
|
|
||||||
COPY LICENSE VERSION ./
|
COPY LICENSE VERSION ./
|
||||||
COPY docker/entrypoint.sh /app/entrypoint.sh
|
COPY docker/entrypoint.sh /app/entrypoint.sh
|
||||||
RUN chmod +x /app/entrypoint.sh
|
RUN chmod +x /app/entrypoint.sh
|
||||||
|
|||||||
+4
-7
@@ -1,15 +1,12 @@
|
|||||||
name: instagram-hdr
|
name: instameex
|
||||||
|
|
||||||
services:
|
services:
|
||||||
hdr-web:
|
hdr-web:
|
||||||
build:
|
build:
|
||||||
context: ..
|
context: ..
|
||||||
dockerfile: docker/Dockerfile
|
dockerfile: docker/Dockerfile
|
||||||
container_name: instagram-hdr-web
|
image: instameex
|
||||||
|
container_name: instameex
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "5050:5000" # host:container — change 5050 if that's taken too
|
- "5050:5000" # host:container — change 5050 if that's taken too
|
||||||
# Put a reverse proxy (Traefik/nginx/SWAG) in front of this if you
|
|
||||||
# want it reachable outside your LAN — the Flask app itself has no
|
|
||||||
# auth and no rate limiting, so keep it internal-only or add both
|
|
||||||
# before exposing it further than your own network.
|
|
||||||
@@ -6,13 +6,12 @@ NC="\033[0m"
|
|||||||
|
|
||||||
VERSION=$(cat /app/VERSION)
|
VERSION=$(cat /app/VERSION)
|
||||||
|
|
||||||
echo -e "${CYAN}╭──────────────────────────────────────────────────╮${NC}"
|
echo -e "${CYAN}╭──────────────────────────────────────────────╮${NC}"
|
||||||
echo -e "${CYAN}│${NC} Instagram ${CYAN}HDR${NC} Assembler — version ${VERSION} ${CYAN}│${NC}"
|
echo -e "${CYAN}│${NC} Instam${CYAN}eex${NC} - Version ${VERSION} ${CYAN}│${NC}"
|
||||||
echo -e "${CYAN}├──────────────────────────────────────────────────┤${NC}"
|
echo -e "${CYAN}├──────────────────────────────────────────────┤${NC}"
|
||||||
echo -e "${CYAN}│${NC} License: MIT (see LICENSE) ${CYAN}│${NC}"
|
echo -e "${CYAN}│${NC} Source: https://git.djeex.fr/Djeex/instameex ${CYAN}│${NC}"
|
||||||
echo -e "${CYAN}│${NC} Gain-map pipeline adapted from: ${CYAN}│${NC}"
|
echo -e "${CYAN}│${NC} Mirror: https://github.com/Djeex/instameex ${CYAN}│${NC}"
|
||||||
echo -e "${CYAN}│${NC} kostis-kounadis/instagram-hdr-assembler ${CYAN}│${NC}"
|
echo -e "${CYAN}╰──────────────────────────────────────────────╯${NC}"
|
||||||
echo -e "${CYAN}╰──────────────────────────────────────────────────╯${NC}"
|
|
||||||
|
|
||||||
echo -e "[~] Checking required external tools..."
|
echo -e "[~] Checking required external tools..."
|
||||||
missing=""
|
missing=""
|
||||||
@@ -27,5 +26,5 @@ if [ -n "$missing" ]; then
|
|||||||
fi
|
fi
|
||||||
echo -e "[✓] Required tools found: ultrahdr_app, exiftool"
|
echo -e "[✓] Required tools found: ultrahdr_app, exiftool"
|
||||||
|
|
||||||
echo -e "[~] Starting Instagram HDR Assembler web server..."
|
echo -e "[~] Starting Instameex web server..."
|
||||||
exec gunicorn --bind 0.0.0.0:5000 --workers 1 --threads 4 --timeout 120 main:app
|
exec gunicorn --bind 0.0.0.0:5000 --workers 1 --threads 4 --timeout 120 main:app
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 140 KiB |
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
Instagram HDR Assembler — Web Front
|
Instameex - Web Front
|
||||||
------------------------------------
|
------------------------------------
|
||||||
Flask front end for assembling Instagram-compliant HDR JPEGs (gain map
|
Flask front end for assembling Instagram-compliant HDR JPEGs (gain map
|
||||||
injection adapted from kostis-kounadis/instagram-hdr-assembler — see
|
injection adapted from kostis-kounadis/instagram-hdr-assembler — see
|
||||||
@@ -8,18 +8,21 @@ LICENSE). Gives you a browser form instead of a terminal, and
|
|||||||
surfaces the assembly report (including the "GainMapMin is negative"
|
surfaces the assembly report (including the "GainMapMin is negative"
|
||||||
warning) on a results page.
|
warning) on a results page.
|
||||||
|
|
||||||
This file is just the entry point — see app/ for the app itself:
|
This file is just the entry point — see src/script/app/ for the app itself:
|
||||||
app/config.py constants
|
src/script/app/config.py constants
|
||||||
app/jobs.py in-memory job registry + heartbeat/reaper
|
src/script/app/jobs.py in-memory job registry + heartbeat/reaper
|
||||||
app/validation.py upload + Instagram resolution checks
|
src/script/app/validation.py upload + Instagram resolution checks
|
||||||
app/assembler.py gain-map assembly pipeline
|
src/script/app/assembler.py gain-map assembly pipeline
|
||||||
app/routes.py Flask routes
|
src/script/app/routes.py Flask routes
|
||||||
|
|
||||||
Run directly: python3 main.py
|
Run directly: python3 main.py
|
||||||
Run in Docker: see Dockerfile (gunicorn main:app)
|
Run in Docker: see Dockerfile (gunicorn main:app)
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parent / "src" / "script"))
|
||||||
|
|
||||||
from app import create_app
|
from app import create_app
|
||||||
from app.assembler import check_dependencies
|
from app.assembler import check_dependencies
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 298 KiB |
@@ -1,17 +1,25 @@
|
|||||||
:root {
|
:root {
|
||||||
--bg: #14161a;
|
--bg: #111010;
|
||||||
--surface: #1c1f25;
|
--surface: rgb(67 67 67 / 26%);
|
||||||
--surface-raised: #242830;
|
--surface-raised: #1f2223;
|
||||||
--border: #33373f;
|
--surface-focus: #161616;
|
||||||
--text: #eef1f4;
|
--border: #2f2e2e80;
|
||||||
--text-muted: #8b93a0;
|
--border-solid: #585858;
|
||||||
--accent: #2dd4bf;
|
--text: #fbfbfb;
|
||||||
--accent-dim: #2a8577;
|
--text-muted: #9a9a9a;
|
||||||
--ok: #7fa37a;
|
--placeholder: #585858;
|
||||||
--err: #c4604a;
|
--accent: #55c3ec;
|
||||||
--serif: Charter, "Iowan Old Style", "Palatino Linotype", Georgia, ui-serif, serif;
|
--accent-start: #26c4ff;
|
||||||
|
--accent-end: #016074;
|
||||||
|
--accent-hover-start: #72d9ff;
|
||||||
|
--accent-hover-end: #26657e;
|
||||||
|
--gold: #ffc700;
|
||||||
|
--ok: #049b3d;
|
||||||
|
--ok-bright: #28c76f;
|
||||||
|
--err: #dc3545;
|
||||||
|
--err-dim: rgb(121 26 19);
|
||||||
--mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;
|
--mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;
|
||||||
--sans: -apple-system, "Segoe UI", system-ui, sans-serif;
|
--sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
* { box-sizing: border-box; }
|
* { box-sizing: border-box; }
|
||||||
@@ -20,8 +28,6 @@ body {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 3rem 1.5rem 5rem;
|
padding: 3rem 1.5rem 5rem;
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
background-image:
|
|
||||||
radial-gradient(ellipse 900px 500px at 50% -10%, rgba(45, 212, 191, 0.07), transparent);
|
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
font-family: var(--sans);
|
font-family: var(--sans);
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
@@ -34,19 +40,28 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.eyebrow {
|
.eyebrow {
|
||||||
font-family: var(--mono);
|
font-family: var(--sans);
|
||||||
font-size: 0.72rem;
|
font-size: 0.78rem;
|
||||||
letter-spacing: 0.14em;
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
margin: 0 0 0.6rem;
|
margin: 0 0 0.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
display: block;
|
||||||
|
width: 290px;
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
margin: 0 0 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-family: var(--serif);
|
font-family: var(--sans);
|
||||||
font-weight: 500;
|
font-weight: 600;
|
||||||
font-size: 2.1rem;
|
font-size: 1.7rem;
|
||||||
line-height: 1.15;
|
line-height: 1.2;
|
||||||
margin: 0 0 0.5rem;
|
margin: 0 0 0.5rem;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
@@ -54,14 +69,15 @@ h1 {
|
|||||||
.subtitle {
|
.subtitle {
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
margin: 0 0 2.5rem;
|
margin: 0 0 2rem;
|
||||||
max-width: 46ch;
|
max-width: 46ch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 10px;
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
padding: 1.75rem;
|
padding: 1.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,32 +107,45 @@ form { display: block; }
|
|||||||
width: 1.5rem;
|
width: 1.5rem;
|
||||||
height: 1.5rem;
|
height: 1.5rem;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border-solid);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: var(--surface-raised);
|
background: #2d2d2d;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
transition: background 0.2s ease, color 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.duo:first-child .remove-duo { display: none; }
|
.duo:first-child .remove-duo { display: none; }
|
||||||
.remove-duo:hover { color: var(--err); border-color: var(--err); }
|
.remove-duo:hover { background: var(--err-dim); color: var(--text); }
|
||||||
|
|
||||||
.add-duo {
|
.add-duo {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 1px dashed var(--border);
|
border: 1px solid var(--border-solid);
|
||||||
color: var(--text-muted);
|
color: var(--text);
|
||||||
padding: 0.6rem 1rem;
|
padding: 0.6rem 1rem;
|
||||||
border-radius: 7px;
|
border-radius: 30px;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: border-color 0.15s ease, color 0.15s ease;
|
transition: background 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-duo:hover { color: var(--text); border-color: var(--accent-dim); }
|
.add-duo:hover { background: #2d2d2d; }
|
||||||
|
|
||||||
|
.checkbox-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin: 1rem 0;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-row input { cursor: pointer; accent-color: var(--accent); }
|
||||||
|
|
||||||
.dropzones {
|
.dropzones {
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -131,7 +160,7 @@ form { display: block; }
|
|||||||
|
|
||||||
.dropzone {
|
.dropzone {
|
||||||
position: relative;
|
position: relative;
|
||||||
border: 1px dashed var(--border);
|
border: 1px dashed var(--border-solid);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 1.25rem 1rem;
|
padding: 1.25rem 1rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -140,20 +169,21 @@ form { display: block; }
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dropzone:has(input:focus-visible) {
|
.dropzone:has(input:focus-visible) {
|
||||||
outline: 2px solid var(--accent);
|
border-color: var(--accent);
|
||||||
outline-offset: 2px;
|
background: var(--surface-focus);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropzone.has-file {
|
.dropzone.has-file {
|
||||||
border-color: var(--accent-dim);
|
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
|
border-color: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropzone label.tag {
|
.dropzone .tag {
|
||||||
display: block;
|
display: block;
|
||||||
font-family: var(--mono);
|
font-family: var(--sans);
|
||||||
font-size: 0.68rem;
|
font-size: 0.72rem;
|
||||||
letter-spacing: 0.1em;
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
@@ -185,26 +215,27 @@ form { display: block; }
|
|||||||
button.run {
|
button.run {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: 1.5rem;
|
margin-top: 1.5rem;
|
||||||
background: var(--accent);
|
background: linear-gradient(135deg, var(--accent-start), var(--accent-end));
|
||||||
color: #06211d;
|
color: #fff;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 0.75rem 1rem;
|
padding: 0.8rem 1rem;
|
||||||
border-radius: 7px;
|
border-radius: 30px;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.15s ease;
|
box-shadow: 0 4px 16px rgba(38, 196, 255, 0.15);
|
||||||
|
transition: background 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.run:hover { background: #5eead4; }
|
button.run:hover { background: linear-gradient(135deg, var(--accent-hover-start), var(--accent-hover-end)); }
|
||||||
button.run:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }
|
button.run:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }
|
||||||
|
|
||||||
.error-banner {
|
.error-banner {
|
||||||
background: rgba(196, 96, 74, 0.12);
|
background: rgba(220, 53, 69, 0.12);
|
||||||
border: 1px solid rgba(196, 96, 74, 0.4);
|
border: 1px solid rgba(220, 53, 69, 0.4);
|
||||||
color: #e2a795;
|
color: #f28b95;
|
||||||
padding: 0.7rem 0.9rem;
|
padding: 0.7rem 0.9rem;
|
||||||
border-radius: 7px;
|
border-radius: 8px;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
margin-bottom: 1.25rem;
|
margin-bottom: 1.25rem;
|
||||||
}
|
}
|
||||||
@@ -213,36 +244,36 @@ button.run:focus-visible { outline: 2px solid var(--text); outline-offset: 2px;
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
font-family: var(--mono);
|
font-family: var(--sans);
|
||||||
|
font-weight: 700;
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.02em;
|
||||||
text-transform: uppercase;
|
padding: 0.35rem 0.9rem;
|
||||||
padding: 0.35rem 0.7rem;
|
border-radius: 30px;
|
||||||
border-radius: 5px;
|
|
||||||
margin-bottom: 1.25rem;
|
margin-bottom: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status.ok { background: rgba(127, 163, 122, 0.14); color: var(--ok); }
|
.status.ok { background: rgba(4, 155, 61, 0.16); color: var(--ok-bright); }
|
||||||
.status.err { background: rgba(196, 96, 74, 0.14); color: var(--err); }
|
.status.err { background: rgba(220, 53, 69, 0.14); color: #f28b95; }
|
||||||
|
|
||||||
.warning-banner {
|
.warning-banner {
|
||||||
background: rgba(45, 212, 191, 0.1);
|
background: rgba(255, 199, 0, 0.1);
|
||||||
border: 1px solid rgba(45, 212, 191, 0.35);
|
border: 1px solid rgba(255, 199, 0, 0.35);
|
||||||
color: var(--accent);
|
color: var(--gold);
|
||||||
padding: 0.7rem 0.9rem;
|
padding: 0.7rem 0.9rem;
|
||||||
border-radius: 7px;
|
border-radius: 8px;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
margin-bottom: 1.25rem;
|
margin-bottom: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre.log {
|
pre.log {
|
||||||
background: #0e1015;
|
background: #0a0a0a;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 7px;
|
border-radius: 8px;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
font-family: var(--mono);
|
font-family: var(--mono);
|
||||||
font-size: 0.76rem;
|
font-size: 0.76rem;
|
||||||
color: #b6c0c8;
|
color: #c7c7c7;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
@@ -261,24 +292,24 @@ pre.log {
|
|||||||
.btn-secondary {
|
.btn-secondary {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
padding: 0.65rem 1.1rem;
|
padding: 0.65rem 1.3rem;
|
||||||
border-radius: 7px;
|
border-radius: 30px;
|
||||||
font-size: 0.88rem;
|
font-size: 0.88rem;
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-download {
|
.btn-download {
|
||||||
background: var(--accent);
|
background: linear-gradient(135deg, var(--accent-start), var(--accent-end));
|
||||||
color: #06211d;
|
color: #fff;
|
||||||
}
|
}
|
||||||
.btn-download:hover { background: #5eead4; }
|
.btn-download:hover { background: linear-gradient(135deg, var(--accent-hover-start), var(--accent-hover-end)); }
|
||||||
|
|
||||||
.btn-secondary {
|
.btn-secondary {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--text-muted);
|
color: var(--text);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border-solid);
|
||||||
}
|
}
|
||||||
.btn-secondary:hover { color: var(--text); border-color: var(--accent-dim); }
|
.btn-secondary:hover { background: #2d2d2d; }
|
||||||
|
|
||||||
.footnote {
|
.footnote {
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
@@ -290,6 +321,7 @@ pre.log {
|
|||||||
.footnote code {
|
.footnote code {
|
||||||
font-family: var(--mono);
|
font-family: var(--mono);
|
||||||
background: var(--surface-raised);
|
background: var(--surface-raised);
|
||||||
|
color: var(--accent);
|
||||||
padding: 0.1rem 0.35rem;
|
padding: 0.1rem 0.35rem;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
@@ -300,4 +332,30 @@ pre.log {
|
|||||||
font-family: var(--mono);
|
font-family: var(--mono);
|
||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.repo-links {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-top: 0.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-links a {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-links a:hover { color: var(--text); }
|
||||||
|
|
||||||
|
.repo-links .icon {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-links .icon img { width: 100%; height: 100%; }
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg fill="#48cf51ff" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Gitea icon</title><path d="M4.186 5.421C2.341 5.417-.13 6.59.006 9.531c.213 4.594 4.92 5.02 6.801 5.057.206.862 2.42 3.834 4.059 3.99h7.18c4.306-.286 7.53-13.022 5.14-13.07-3.953.186-6.296.28-8.305.296v3.975l-.626-.277-.004-3.696c-2.306-.001-4.336-.108-8.189-.298-.482-.003-1.154-.085-1.876-.087zm.261 1.625h.22c.262 2.355.688 3.732 1.55 5.836-2.2-.26-4.072-.899-4.416-3.285-.178-1.235.422-2.524 2.646-2.552zm8.557 2.315c.15.002.303.03.447.096l.749.323-.537.979a.672.597 0 0 0-.241.038.672.597 0 0 0-.405.764.672.597 0 0 0 .112.174l-.926 1.686a.672.597 0 0 0-.222.038.672.597 0 0 0-.405.764.672.597 0 0 0 .86.36.672.597 0 0 0 .404-.765.672.597 0 0 0-.158-.22l.902-1.642a.672.597 0 0 0 .293-.03.672.597 0 0 0 .213-.112c.348.146.633.265.838.366.308.152.417.253.45.365.033.11-.003.322-.177.694-.13.277-.345.67-.599 1.133a.672.597 0 0 0-.251.038.672.597 0 0 0-.405.764.672.597 0 0 0 .86.36.672.597 0 0 0 .404-.764.672.597 0 0 0-.137-.202c.251-.458.467-.852.606-1.148.188-.402.286-.701.2-.99-.086-.289-.35-.477-.7-.65-.23-.113-.517-.233-.86-.377a.672.597 0 0 0-.038-.239.672.597 0 0 0-.145-.209l.528-.963 2.924 1.263c.528.229.746.79.49 1.26l-2.01 3.68c-.257.469-.888.663-1.416.435l-4.137-1.788c-.528-.228-.747-.79-.49-1.26l2.01-3.679c.176-.323.53-.515.905-.53h.064z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
|
||||||
|
<title>github [#142]</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs>
|
||||||
|
|
||||||
|
</defs>
|
||||||
|
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="Dribbble-Light-Preview" transform="translate(-140.000000, -7559.000000)" fill="#ffffffff">
|
||||||
|
<g id="icons" transform="translate(56.000000, 160.000000)">
|
||||||
|
<path d="M94,7399 C99.523,7399 104,7403.59 104,7409.253 C104,7413.782 101.138,7417.624 97.167,7418.981 C96.66,7419.082 96.48,7418.762 96.48,7418.489 C96.48,7418.151 96.492,7417.047 96.492,7415.675 C96.492,7414.719 96.172,7414.095 95.813,7413.777 C98.04,7413.523 100.38,7412.656 100.38,7408.718 C100.38,7407.598 99.992,7406.684 99.35,7405.966 C99.454,7405.707 99.797,7404.664 99.252,7403.252 C99.252,7403.252 98.414,7402.977 96.505,7404.303 C95.706,7404.076 94.85,7403.962 94,7403.958 C93.15,7403.962 92.295,7404.076 91.497,7404.303 C89.586,7402.977 88.746,7403.252 88.746,7403.252 C88.203,7404.664 88.546,7405.707 88.649,7405.966 C88.01,7406.684 87.619,7407.598 87.619,7408.718 C87.619,7412.646 89.954,7413.526 92.175,7413.785 C91.889,7414.041 91.63,7414.493 91.54,7415.156 C90.97,7415.418 89.522,7415.871 88.63,7414.304 C88.63,7414.304 88.101,7413.319 87.097,7413.247 C87.097,7413.247 86.122,7413.234 87.029,7413.87 C87.029,7413.87 87.684,7414.185 88.139,7415.37 C88.139,7415.37 88.726,7417.2 91.508,7416.58 C91.513,7417.437 91.522,7418.245 91.522,7418.489 C91.522,7418.76 91.338,7419.077 90.839,7418.982 C86.865,7417.627 84,7413.783 84,7409.253 C84,7403.59 88.478,7399 94,7399" id="github-[#142]">
|
||||||
|
|
||||||
|
</path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,153 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 7716.9 1000">
|
||||||
|
<!-- Generator: Adobe Illustrator 30.7.0, SVG Export Plug-In . SVG Version: 2.1.4 Build 114) -->
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.st0 {
|
||||||
|
fill: #55c3ec;
|
||||||
|
}
|
||||||
|
|
||||||
|
.st1 {
|
||||||
|
fill: url(#Dégradé_sans_nom_305);
|
||||||
|
stroke: url(#Dégradé_sans_nom_335);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st1, .st2, .st3, .st4, .st5, .st6, .st7, .st8, .st9, .st10, .st11, .st12 {
|
||||||
|
stroke-miterlimit: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.st2 {
|
||||||
|
fill: url(#Dégradé_sans_nom_308);
|
||||||
|
stroke: url(#Dégradé_sans_nom_338);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st3 {
|
||||||
|
fill: url(#Dégradé_sans_nom_301);
|
||||||
|
stroke: url(#Dégradé_sans_nom_331);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st4 {
|
||||||
|
fill: url(#Dégradé_sans_nom_309);
|
||||||
|
stroke: url(#Dégradé_sans_nom_339);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st5 {
|
||||||
|
fill: url(#Dégradé_sans_nom_303);
|
||||||
|
stroke: url(#Dégradé_sans_nom_333);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st6 {
|
||||||
|
fill: url(#Dégradé_sans_nom_304);
|
||||||
|
stroke: url(#Dégradé_sans_nom_334);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st7 {
|
||||||
|
fill: url(#Dégradé_sans_nom_302);
|
||||||
|
stroke: url(#Dégradé_sans_nom_332);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st8 {
|
||||||
|
fill: url(#Dégradé_sans_nom_307);
|
||||||
|
stroke: url(#Dégradé_sans_nom_337);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st9 {
|
||||||
|
fill: url(#Dégradé_sans_nom_306);
|
||||||
|
stroke: url(#Dégradé_sans_nom_336);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st13 {
|
||||||
|
fill: url(#Dégradé_sans_nom_391);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st10 {
|
||||||
|
fill: url(#Dégradé_sans_nom_30);
|
||||||
|
stroke: url(#Dégradé_sans_nom_33);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st11 {
|
||||||
|
fill: url(#Dégradé_sans_nom_3011);
|
||||||
|
stroke: url(#Dégradé_sans_nom_3311);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st12 {
|
||||||
|
fill: url(#Dégradé_sans_nom_3010);
|
||||||
|
stroke: url(#Dégradé_sans_nom_3310);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st14 {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.st15 {
|
||||||
|
fill: url(#Dégradé_sans_nom_39);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_39" data-name="Dégradé sans nom 39" x1="244.5" y1="503" x2="746.5" y2="503" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="#55c2eb"/>
|
||||||
|
<stop offset="1" stop-color="#1d70b7" stop-opacity=".8"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_391" data-name="Dégradé sans nom 39" x1="398.9" y1="504.3" x2="594.6" y2="504.3" xlink:href="#Dégradé_sans_nom_39"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_30" data-name="Dégradé sans nom 30" x1="74" y1="560.2" x2="105.7" y2="591.8" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="#55c2eb"/>
|
||||||
|
<stop offset="1" stop-color="#1d70b7" stop-opacity=".8"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_33" data-name="Dégradé sans nom 33" x1="73.7" y1="559.9" x2="106" y2="592.2" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="#55c2eb"/>
|
||||||
|
<stop offset="1" stop-color="#1d70b7" stop-opacity=".5"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_301" data-name="Dégradé sans nom 30" x1="1430.9" y1="2772.1" x2="1449.5" y2="2790.7" gradientTransform="translate(1863.806 -2030.0199) rotate(60)" xlink:href="#Dégradé_sans_nom_30"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_331" data-name="Dégradé sans nom 33" x1="1430.6" y1="2771.8" x2="1449.9" y2="2791" gradientTransform="translate(1863.806 -2030.0199) rotate(60)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_302" data-name="Dégradé sans nom 30" x1="3365.6" y1="3054.2" x2="3405.2" y2="3093.8" gradientTransform="translate(3985.7456 -1782.7635) rotate(105)" xlink:href="#Dégradé_sans_nom_30"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_332" data-name="Dégradé sans nom 33" x1="3365.2" y1="3053.9" x2="3405.5" y2="3094.2" gradientTransform="translate(3985.7456 -1782.7635) rotate(105)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_303" data-name="Dégradé sans nom 30" x1="3101.3" y1="2411.8" x2="3140.9" y2="2451.4" gradientTransform="translate(3985.7456 -1782.7635) rotate(105)" xlink:href="#Dégradé_sans_nom_30"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_333" data-name="Dégradé sans nom 33" x1="3100.9" y1="2411.4" x2="3141.2" y2="2451.7" gradientTransform="translate(3985.7456 -1782.7635) rotate(105)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_304" data-name="Dégradé sans nom 30" x1="1757.9" y1="2114.1" x2="1776.4" y2="2132.7" gradientTransform="translate(1863.806 -2030.0199) rotate(60)" xlink:href="#Dégradé_sans_nom_30"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_334" data-name="Dégradé sans nom 33" x1="1757.5" y1="2113.8" x2="1776.8" y2="2133.1" gradientTransform="translate(1863.806 -2030.0199) rotate(60)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_305" data-name="Dégradé sans nom 30" x1="824.6" y1="682.3" x2="856.3" y2="713.9" xlink:href="#Dégradé_sans_nom_30"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_335" data-name="Dégradé sans nom 33" x1="824.3" y1="681.9" x2="856.6" y2="714.3" xlink:href="#Dégradé_sans_nom_33"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_306" data-name="Dégradé sans nom 30" x1="3463.8" y1="2696" x2="3495.6" y2="2727.9" gradientTransform="translate(3985.7456 -1782.7635) rotate(105)" xlink:href="#Dégradé_sans_nom_30"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_336" data-name="Dégradé sans nom 33" x1="3463.4" y1="2695.7" x2="3496" y2="2728.2" gradientTransform="translate(3985.7456 -1782.7635) rotate(105)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_307" data-name="Dégradé sans nom 30" x1="1813.2" y1="2573.7" x2="1831.8" y2="2592.3" gradientTransform="translate(1863.806 -2030.0199) rotate(60)" xlink:href="#Dégradé_sans_nom_30"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_337" data-name="Dégradé sans nom 33" x1="1812.8" y1="2573.4" x2="1832.1" y2="2592.6" gradientTransform="translate(1863.806 -2030.0199) rotate(60)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_308" data-name="Dégradé sans nom 30" x1="4603" y1="48.2" x2="4642.6" y2="87.7" gradientTransform="translate(5288.3589 605.3909) rotate(-175.4391)" xlink:href="#Dégradé_sans_nom_30"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_338" data-name="Dégradé sans nom 33" x1="4602.7" y1="47.8" x2="4643" y2="88.1" gradientTransform="translate(5288.3589 605.3909) rotate(-175.4391)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_309" data-name="Dégradé sans nom 30" x1="4500.7" y1="1503.6" x2="4519.3" y2="1522.2" gradientTransform="translate(5147.0477 -1526.227) rotate(139.5609)" xlink:href="#Dégradé_sans_nom_30"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_339" data-name="Dégradé sans nom 33" x1="4500.4" y1="1503.3" x2="4519.6" y2="1522.5" gradientTransform="translate(5147.0477 -1526.227) rotate(139.5609)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_3010" data-name="Dégradé sans nom 30" x1="2951" y1="2709.4" x2="2980.8" y2="2739.3" gradientTransform="translate(3343.1844 -2356.0981) rotate(97.9139)" xlink:href="#Dégradé_sans_nom_30"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_3310" data-name="Dégradé sans nom 33" x1="2950.6" y1="2709.1" x2="2981.2" y2="2739.6" gradientTransform="translate(3343.1844 -2356.0981) rotate(97.9139)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_3011" data-name="Dégradé sans nom 30" x1="1450.8" y1="2212.6" x2="1469.4" y2="2231.2" gradientTransform="translate(1206.9506 -2339.7003) rotate(52.9139)" xlink:href="#Dégradé_sans_nom_30"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_3311" data-name="Dégradé sans nom 33" x1="1450.4" y1="2212.3" x2="1469.7" y2="2231.6" gradientTransform="translate(1206.9506 -2339.7003) rotate(52.9139)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||||
|
</defs>
|
||||||
|
<g id="Calque_11" data-name="Calque_1">
|
||||||
|
<circle class="st14" cx="499.5" cy="499.5" r="499.5"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st14" d="M1417.4,957.4V59h228.5v898.4h-228.5Z"/>
|
||||||
|
<path class="st14" d="M1767.3,957.4V279.4h221v119.5h4.4c17.4-42.8,43.4-75.7,77.8-99,34.4-23.2,77.2-34.9,128.2-34.9,74.7,0,132.3,22.5,172.8,67.5s60.7,107.8,60.7,188.3v436.4h-221v-389.1c0-38.6-9.1-68.8-27.4-90.6-18.3-21.8-45.5-32.7-81.5-32.7s-43.4,5.4-60.4,16.2-30.2,25.7-39.5,44.8c-9.3,19.1-14,41.3-14,66.6v384.7h-221Z"/>
|
||||||
|
<path class="st14" d="M2833.1,971.7c-67.2,0-124.1-9.2-170.6-27.7-46.5-18.5-82.6-44.3-108.3-77.5-25.7-33.2-41.1-71.8-46.1-115.8l-.6-5.6h214.2l1.2,5c5.4,22.8,17.1,40.7,35.2,53.5,18,12.9,43,19.3,75,19.3s36.3-2.3,50.4-6.8,24.9-11.1,32.4-19.6,11.2-18.4,11.2-29.6v-.6c0-14.9-6.1-27.1-18.4-36.4-12.2-9.3-33.5-17.1-63.8-23.4l-119.5-23c-44-8.7-81-22.1-111.1-40.2-30.1-18-52.7-40.4-67.8-66.9-15.2-26.6-22.7-57.1-22.7-91.5v-.6c0-45.2,12.1-84.1,36.4-116.7,24.3-32.6,59.1-57.8,104.6-75.6,45.4-17.8,98.9-26.8,160.3-26.8s120.7,10.1,165.3,30.2c44.6,20.1,78.5,46.9,101.8,80.3,23.2,33.4,35.1,70.5,35.5,111.1v5h-201.7l-.6-5c-2.1-19.9-11.6-37-28.6-51.4-17-14.3-40.9-21.5-71.6-21.5s-33.4,2.4-46.7,7.2-23.6,11.4-30.8,19.9-10.9,18.8-10.9,30.8v.6c0,9.5,2.5,18,7.5,25.5,5,7.5,13.7,13.9,26.1,19.3,12.5,5.4,29.5,10.4,51.1,15l118.9,23.7c72.6,14.1,125.2,36,157.8,65.7,32.6,29.7,48.9,69.8,48.9,120.5v.6c0,46.5-13.3,87-39.8,121.4-26.6,34.5-63.3,61-110.2,79.7-46.9,18.7-101.5,28-163.7,28Z"/>
|
||||||
|
<path class="st14" d="M3519.2,971.7c-81.8,0-142-16.3-180.9-48.9s-58.2-87-58.2-163.4v-318.1h-92.1v-161.9h92.1V123.1h221.6v156.3h121.4v161.9h-121.4v288.2c0,29.1,6.6,49.7,19.9,62s33.2,18.4,59.8,18.4,16.8-.3,23-.9,12.5-1.3,18.7-2.2v156.9c-12,2.1-26.6,4-43.6,5.6s-37.1,2.5-60.4,2.5Z"/>
|
||||||
|
<path class="st14" d="M3914.5,966.7c-45.2,0-85.1-8.8-119.5-26.5-34.5-17.6-61.3-41.9-80.6-72.8-19.3-30.9-29-66.1-29-105.5v-.6c0-42.8,10.7-79.1,32.1-108.9s52.4-53.4,93.1-70.7,90.1-27.7,148.2-31.5l271.4-17.4v128.2l-235.9,16.2c-30.7,1.7-53.8,8.6-69.1,20.9-15.4,12.2-23,28.5-23,48.9v.6c0,21.2,8.2,37.5,24.6,48.9s38.1,17.1,65.1,17.1,44.5-4.2,62.6-12.8c18.1-8.5,32.4-20.3,43-35.5,10.6-15.1,15.9-32,15.9-50.7v-207.9c0-24.5-7.9-43.9-23.7-58.2-15.8-14.3-38.8-21.5-69.1-21.5s-53.3,6-70.4,18.1-27.4,26.8-31.1,44.2l-1.2,6.2h-199.2l.6-8.1c3.7-44.4,18.2-83.2,43.6-116.4s60.7-59.1,106.2-77.8c45.4-18.7,99.7-28,162.8-28s113.6,9.5,159.1,28.6c45.4,19.1,80.7,45.9,105.8,80.3,25.1,34.5,37.7,74.5,37.7,120.2v463.2h-221v-95.9h-4.4c-12.9,22-28.8,40.9-47.9,56.7-19.1,15.8-40.9,27.8-65.4,36.1s-51.5,12.5-80.9,12.5Z"/>
|
||||||
|
<path class="st14" d="M4439.3,957.4V279.4h221v122h4.4c13.3-41.5,36.9-74.6,71-99.3s74.3-37,120.8-37,61.2,5.7,86.5,17.1,46.5,27.8,63.5,49.2,28.6,47.6,34.9,78.8h4.4c8.7-29.5,23.1-55,43.2-76.6s44.4-38.4,72.8-50.4,59.5-18.1,93.1-18.1,83.2,9.7,116.4,29,59.1,46.3,77.8,80.9c18.7,34.7,28,75,28,121.1v461.3h-221v-404.7c0-23.7-3.5-43.5-10.6-59.5-7.1-16-17.6-28-31.8-36.1s-31.8-12.1-52.9-12.1-36.6,4.6-51.4,13.7-26,21.8-33.9,38c-7.9,16.2-11.8,35.7-11.8,58.5v402.2h-211v-406.5c0-23.2-3.6-42.6-10.9-58.2-7.3-15.6-17.8-27.4-31.8-35.5s-31-12.1-51.3-12.1-37.3,4.7-52,14-26.1,22.3-34.2,38.9c-8.1,16.6-12.1,36.1-12.1,58.5v400.9h-221Z"/>
|
||||||
|
<path class="st0" d="M5904.8,971.7c-71.4,0-132.8-14.2-184.3-42.6s-91-69-118.6-121.7-41.4-115.6-41.4-188.6v-.6c0-72.6,13.8-135.3,41.4-188,27.6-52.7,66.6-93.4,117-122,50.4-28.6,109.9-43,178.4-43s127.8,14,178.1,42,89,67.4,116.4,118.3,41.1,110.5,41.1,179v64.7h-564v-130.7h460.7l-102.7,122.6v-85.3c0-32.8-5.2-60.7-15.6-83.7-10.4-23-24.9-40.5-43.6-52.3-18.7-11.8-40.5-17.8-65.4-17.8s-46.8,6-65.7,18.1-33.5,29.6-43.9,52.6c-10.4,23-15.6,50.7-15.6,83.1v85.9c0,32,5.3,59.4,15.9,82.2,10.6,22.8,25.7,40.4,45.4,52.6s43.3,18.4,70.7,18.4,43.8-3.7,60.4-11.2c16.6-7.5,29.8-16.6,39.5-27.4s16.3-21,19.6-30.5l.6-2.5h199.2l-1.9,7.5c-5,23.7-14.5,48.2-28.6,73.8-14.1,25.5-33.7,49.5-58.8,71.9s-56.9,40.6-95.2,54.5c-38.4,13.9-84.8,20.8-139.1,20.8Z"/>
|
||||||
|
<path class="st0" d="M6641.3,971.7c-71.4,0-132.8-14.2-184.3-42.6s-91-69-118.6-121.7-41.4-115.6-41.4-188.6v-.6c0-72.6,13.8-135.3,41.4-188,27.6-52.7,66.6-93.4,117-122,50.4-28.6,109.9-43,178.4-43s127.8,14,178.1,42,89,67.4,116.4,118.3,41.1,110.5,41.1,179v64.7h-564v-130.7h460.7l-102.7,122.6v-85.3c0-32.8-5.2-60.7-15.6-83.7-10.4-23-24.9-40.5-43.6-52.3-18.7-11.8-40.5-17.8-65.4-17.8s-46.8,6-65.7,18.1-33.5,29.6-43.9,52.6c-10.4,23-15.6,50.7-15.6,83.1v85.9c0,32,5.3,59.4,15.9,82.2,10.6,22.8,25.7,40.4,45.4,52.6s43.3,18.4,70.7,18.4,43.8-3.7,60.4-11.2c16.6-7.5,29.8-16.6,39.5-27.4s16.3-21,19.6-30.5l.6-2.5h199.2l-1.9,7.5c-5,23.7-14.5,48.2-28.6,73.8-14.1,25.5-33.7,49.5-58.8,71.9s-56.9,40.6-95.2,54.5c-38.4,13.9-84.8,20.8-139.1,20.8Z"/>
|
||||||
|
<path class="st0" d="M7003.7,957.4l203-336.8-202.3-341.2h249.7l100.2,216.7h4.3l100.2-216.7h238.4l-203,336.2,202.3,341.8h-239.1l-111.4-222.9h-4.4l-109,222.9h-229.1Z"/>
|
||||||
|
</g>
|
||||||
|
<circle class="st14" cx="499" cy="499.5" r="499.5"/>
|
||||||
|
<g>
|
||||||
|
<path class="st15" d="M617.8,252h-244.6c-72.1,0-128.7,59.2-128.7,128.7v244.6c0,72.1,59.2,128.7,128.7,128.7h244.6c72.1,0,128.7-59.2,128.7-128.7v-244.6c0-72.1-59.2-128.7-128.7-128.7ZM496.8,656.2c-82.4,0-151.9-69.5-151.9-151.9s66.9-151.9,151.9-151.9,151.9,69.5,151.9,151.9-69.5,151.9-151.9,151.9ZM659,378.2c-20.6,0-36-15.4-36-36s15.4-36,36-36,36,15.4,36,36c0,20.6-15.4,36-36,36Z"/>
|
||||||
|
<path class="st13" d="M496.8,406.5c-54.1,0-97.8,43.8-97.8,97.8s43.8,97.8,97.8,97.8,97.8-43.8,97.8-97.8-43.8-97.8-97.8-97.8Z"/>
|
||||||
|
</g>
|
||||||
|
<circle class="st10" cx="89.9" cy="576" r="22.4"/>
|
||||||
|
<circle class="st3" cx="175.1" cy="607.9" r="13.1"/>
|
||||||
|
<circle class="st7" cx="140.3" cy="691.6" r="28"/>
|
||||||
|
<circle class="st5" cx="829.2" cy="602.6" r="28"/>
|
||||||
|
<circle class="st6" cx="908.4" cy="562.1" r="13.1"/>
|
||||||
|
<circle class="st1" cx="840.4" cy="698.1" r="22.4"/>
|
||||||
|
<circle class="st9" cx="465.6" cy="876.5" r="22.5"/>
|
||||||
|
<circle class="st8" cx="538.1" cy="839.8" r="13.1"/>
|
||||||
|
<circle class="st2" cx="685.6" cy="170.1" r="28"/>
|
||||||
|
<circle class="st4" cx="733.2" cy="247.7" r="13.1"/>
|
||||||
|
<circle class="st12" cx="236.4" cy="206.5" r="21.1"/>
|
||||||
|
<circle class="st11" cx="314.9" cy="164.9" r="13.1"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,141 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 1000 1000">
|
||||||
|
<!-- Generator: Adobe Illustrator 30.7.0, SVG Export Plug-In . SVG Version: 2.1.4 Build 114) -->
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.st0 {
|
||||||
|
fill: url(#Dégradé_sans_nom_5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st1 {
|
||||||
|
fill: url(#Dégradé_sans_nom_264);
|
||||||
|
stroke: url(#Dégradé_sans_nom_334);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st1, .st2, .st3, .st4, .st5, .st6, .st7, .st8, .st9, .st10, .st11, .st12 {
|
||||||
|
stroke-miterlimit: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.st2 {
|
||||||
|
fill: url(#Dégradé_sans_nom_262);
|
||||||
|
stroke: url(#Dégradé_sans_nom_332);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st3 {
|
||||||
|
fill: url(#Dégradé_sans_nom_269);
|
||||||
|
stroke: url(#Dégradé_sans_nom_339);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st4 {
|
||||||
|
fill: url(#Dégradé_sans_nom_263);
|
||||||
|
stroke: url(#Dégradé_sans_nom_333);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st5 {
|
||||||
|
fill: url(#Dégradé_sans_nom_265);
|
||||||
|
stroke: url(#Dégradé_sans_nom_335);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st6 {
|
||||||
|
fill: url(#Dégradé_sans_nom_268);
|
||||||
|
stroke: url(#Dégradé_sans_nom_338);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st7 {
|
||||||
|
fill: url(#Dégradé_sans_nom_261);
|
||||||
|
stroke: url(#Dégradé_sans_nom_331);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st8 {
|
||||||
|
fill: url(#Dégradé_sans_nom_266);
|
||||||
|
stroke: url(#Dégradé_sans_nom_336);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st9 {
|
||||||
|
fill: url(#Dégradé_sans_nom_267);
|
||||||
|
stroke: url(#Dégradé_sans_nom_337);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st10 {
|
||||||
|
fill: url(#Dégradé_sans_nom_26);
|
||||||
|
stroke: url(#Dégradé_sans_nom_33);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st11 {
|
||||||
|
fill: url(#Dégradé_sans_nom_2610);
|
||||||
|
stroke: url(#Dégradé_sans_nom_3310);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st12 {
|
||||||
|
fill: url(#Dégradé_sans_nom_2611);
|
||||||
|
stroke: url(#Dégradé_sans_nom_3311);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st13 {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.st14 {
|
||||||
|
fill: url(#Dégradé_sans_nom_51);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_5" data-name="Dégradé sans nom 5" x1="245" y1="503" x2="747" y2="503" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="#55c3ec"/>
|
||||||
|
<stop offset="1" stop-color="#1d71b8" stop-opacity=".8"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_51" data-name="Dégradé sans nom 5" x1="399.4" y1="504.3" x2="595.1" y2="504.3" xlink:href="#Dégradé_sans_nom_5"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_26" data-name="Dégradé sans nom 26" x1="74.5" y1="560.2" x2="106.2" y2="591.8" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="#55c3ec"/>
|
||||||
|
<stop offset="1" stop-color="#1d71b8" stop-opacity=".8"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_33" data-name="Dégradé sans nom 33" x1="74.2" y1="559.9" x2="106.5" y2="592.2" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="#55c3ec"/>
|
||||||
|
<stop offset="1" stop-color="#1d71b8" stop-opacity=".5"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_261" data-name="Dégradé sans nom 26" x1="158.2" y1="648.8" x2="176.7" y2="667.3" gradientTransform="translate(661.8 133.9) rotate(60)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_331" data-name="Dégradé sans nom 33" x1="157.8" y1="648.4" x2="177.1" y2="667.7" gradientTransform="translate(661.8 133.9) rotate(60)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_262" data-name="Dégradé sans nom 26" x1="210.2" y1="714.7" x2="249.8" y2="754.3" gradientTransform="translate(909.8 659.5) rotate(105)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_332" data-name="Dégradé sans nom 33" x1="209.9" y1="714.3" x2="250.2" y2="754.6" gradientTransform="translate(909.8 659.5) rotate(105)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_263" data-name="Dégradé sans nom 26" x1="-54" y1="72.2" x2="-14.4" y2="111.8" gradientTransform="translate(909.8 659.5) rotate(105)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_333" data-name="Dégradé sans nom 33" x1="-54.4" y1="71.9" x2="-14.1" y2="112.2" gradientTransform="translate(909.8 659.5) rotate(105)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_264" data-name="Dégradé sans nom 26" x1="485.1" y1="-9.2" x2="503.7" y2="9.4" gradientTransform="translate(661.8 133.9) rotate(60)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_334" data-name="Dégradé sans nom 33" x1="484.8" y1="-9.6" x2="504" y2="9.7" gradientTransform="translate(661.8 133.9) rotate(60)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_265" data-name="Dégradé sans nom 26" x1="825.1" y1="682.3" x2="856.8" y2="713.9" xlink:href="#Dégradé_sans_nom_26"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_335" data-name="Dégradé sans nom 33" x1="824.8" y1="681.9" x2="857.1" y2="714.3" xlink:href="#Dégradé_sans_nom_33"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_266" data-name="Dégradé sans nom 26" x1="308.5" y1="356.5" x2="340.3" y2="388.3" gradientTransform="translate(909.8 659.5) rotate(105)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_336" data-name="Dégradé sans nom 33" x1="308.1" y1="356.2" x2="340.7" y2="388.7" gradientTransform="translate(909.8 659.5) rotate(105)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_267" data-name="Dégradé sans nom 26" x1="540.4" y1="450.4" x2="559" y2="469" gradientTransform="translate(661.8 133.9) rotate(60)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_337" data-name="Dégradé sans nom 33" x1="540.1" y1="450" x2="559.4" y2="469.3" gradientTransform="translate(661.8 133.9) rotate(60)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_268" data-name="Dégradé sans nom 26" x1="-336.4" y1="326.9" x2="-296.8" y2="366.5" gradientTransform="translate(342.9 490.5) rotate(-175.4)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_338" data-name="Dégradé sans nom 33" x1="-336.7" y1="326.5" x2="-296.4" y2="366.8" gradientTransform="translate(342.9 490.5) rotate(-175.4)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_269" data-name="Dégradé sans nom 26" x1="115" y1="-29.9" x2="133.6" y2="-11.3" gradientTransform="translate(814.9 151.4) rotate(139.6)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_339" data-name="Dégradé sans nom 33" x1="114.7" y1="-30.2" x2="134" y2="-11" gradientTransform="translate(814.9 151.4) rotate(139.6)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_2610" data-name="Dégradé sans nom 26" x1="94.5" y1="304.2" x2="124.4" y2="334" gradientTransform="translate(568 142) rotate(97.9)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_3310" data-name="Dégradé sans nom 33" x1="94.2" y1="303.8" x2="124.7" y2="334.4" gradientTransform="translate(568 142) rotate(97.9)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_2611" data-name="Dégradé sans nom 26" x1="435.8" y1="254.1" x2="454.4" y2="272.7" gradientTransform="translate(257.1 -349) rotate(52.9)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_3311" data-name="Dégradé sans nom 33" x1="435.5" y1="253.8" x2="454.8" y2="273.1" gradientTransform="translate(257.1 -349) rotate(52.9)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||||
|
</defs>
|
||||||
|
<g id="Calque_1">
|
||||||
|
<circle class="st13" cx="499.5" cy="499.5" r="499.5"/>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M618.3,252h-244.6c-72.1,0-128.7,59.2-128.7,128.7v244.6c0,72.1,59.2,128.7,128.7,128.7h244.6c72.1,0,128.7-59.2,128.7-128.7v-244.6c0-72.1-59.2-128.7-128.7-128.7ZM497.3,656.2c-82.4,0-151.9-69.5-151.9-151.9s66.9-151.9,151.9-151.9,151.9,69.5,151.9,151.9-69.5,151.9-151.9,151.9ZM659.5,378.2c-20.6,0-36-15.4-36-36s15.4-36,36-36,36,15.4,36,36c0,20.6-15.4,36-36,36Z"/>
|
||||||
|
<path class="st14" d="M497.3,406.5c-54.1,0-97.8,43.8-97.8,97.8s43.8,97.8,97.8,97.8,97.8-43.8,97.8-97.8-43.8-97.8-97.8-97.8Z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g id="Calque_2">
|
||||||
|
<g id="Calque_3">
|
||||||
|
<circle class="st10" cx="90.4" cy="576" r="22.4"/>
|
||||||
|
<circle class="st7" cx="175.6" cy="607.9" r="13.1"/>
|
||||||
|
<circle class="st2" cx="140.8" cy="691.6" r="28"/>
|
||||||
|
<circle class="st4" cx="829.7" cy="602.6" r="28"/>
|
||||||
|
<circle class="st1" cx="908.9" cy="562.1" r="13.1"/>
|
||||||
|
<circle class="st5" cx="840.9" cy="698.1" r="22.4"/>
|
||||||
|
<circle class="st8" cx="466.1" cy="876.5" r="22.5"/>
|
||||||
|
<circle class="st9" cx="538.6" cy="839.8" r="13.1"/>
|
||||||
|
<circle class="st6" cx="686.1" cy="170.1" r="28"/>
|
||||||
|
<circle class="st3" cx="733.7" cy="247.7" r="13.1"/>
|
||||||
|
<circle class="st11" cx="236.9" cy="206.5" r="21.1"/>
|
||||||
|
<circle class="st12" cx="315.4" cy="164.9" r="13.1"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 8.8 KiB |
@@ -2,15 +2,15 @@
|
|||||||
from flask import Flask
|
from flask import Flask
|
||||||
|
|
||||||
from . import jobs
|
from . import jobs
|
||||||
from .config import BASE_DIR, MAX_CONTENT_LENGTH, VERSION
|
from .config import ASSETS_DIR, MAX_CONTENT_LENGTH, TEMPLATES_DIR, VERSION
|
||||||
from .routes import register_routes
|
from .routes import register_routes
|
||||||
|
|
||||||
|
|
||||||
def create_app() -> Flask:
|
def create_app() -> Flask:
|
||||||
app = Flask(
|
app = Flask(
|
||||||
__name__,
|
__name__,
|
||||||
template_folder=str(BASE_DIR / "templates"),
|
template_folder=str(TEMPLATES_DIR),
|
||||||
static_folder=str(BASE_DIR / "static"),
|
static_folder=str(ASSETS_DIR),
|
||||||
)
|
)
|
||||||
app.config["MAX_CONTENT_LENGTH"] = MAX_CONTENT_LENGTH
|
app.config["MAX_CONTENT_LENGTH"] = MAX_CONTENT_LENGTH
|
||||||
|
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
"""Shared constants for the HDR-to-Instagram web front."""
|
"""Shared constants for the HDR-to-Instagram web front."""
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
# This file lives at <repo root>/src/script/app/config.py.
|
||||||
JOBS_DIR = BASE_DIR / "jobs"
|
SRC_DIR = Path(__file__).resolve().parent.parent.parent
|
||||||
VERSION = (BASE_DIR / "VERSION").read_text().strip()
|
REPO_ROOT = SRC_DIR.parent
|
||||||
|
|
||||||
|
TEMPLATES_DIR = SRC_DIR / "templates"
|
||||||
|
ASSETS_DIR = SRC_DIR / "assets"
|
||||||
|
JOBS_DIR = REPO_ROOT / "jobs"
|
||||||
|
VERSION = (REPO_ROOT / "VERSION").read_text().strip()
|
||||||
|
|
||||||
ALLOWED_SDR_EXT = {".jpg", ".jpeg"}
|
ALLOWED_SDR_EXT = {".jpg", ".jpeg"}
|
||||||
ALLOWED_HDR_EXT = {".jpg", ".jpeg", ".tif", ".tiff"}
|
ALLOWED_HDR_EXT = {".jpg", ".jpeg", ".tif", ".tiff"}
|
||||||
@@ -8,7 +8,7 @@ from flask import abort, redirect, render_template, request, send_file, url_for
|
|||||||
from . import assembler, jobs, validation
|
from . import assembler, jobs, validation
|
||||||
|
|
||||||
|
|
||||||
def _process_pair(job_dir, index, sdr_file, hdr_file):
|
def _process_pair(job_dir, index, sdr_file, hdr_file, ignore_ig_resolution=False):
|
||||||
"""Runs one SDR/HDR pair through validation + assembly. Returns an item
|
"""Runs one SDR/HDR pair through validation + assembly. Returns an item
|
||||||
dict for the job report; never raises — failures are recorded in it."""
|
dict for the job report; never raises — failures are recorded in it."""
|
||||||
name = hdr_file.filename or sdr_file.filename or f"pair {index}"
|
name = hdr_file.filename or sdr_file.filename or f"pair {index}"
|
||||||
@@ -28,7 +28,7 @@ def _process_pair(job_dir, index, sdr_file, hdr_file):
|
|||||||
sdr_file.save(sdr_path)
|
sdr_file.save(sdr_path)
|
||||||
hdr_file.save(hdr_path)
|
hdr_file.save(hdr_path)
|
||||||
|
|
||||||
ig_error = validation.instagram_resolution_error(sdr_path, hdr_path)
|
ig_error = validation.instagram_resolution_error(sdr_path, hdr_path, ignore_ig_resolution)
|
||||||
if ig_error:
|
if ig_error:
|
||||||
for p in (sdr_path, hdr_path):
|
for p in (sdr_path, hdr_path):
|
||||||
p.unlink(missing_ok=True)
|
p.unlink(missing_ok=True)
|
||||||
@@ -62,11 +62,13 @@ def register_routes(app):
|
|||||||
if len(sdr_files) != len(hdr_files):
|
if len(sdr_files) != len(hdr_files):
|
||||||
return render_template("index.html", error="Each pair needs both an SDR and an HDR file."), 400
|
return render_template("index.html", error="Each pair needs both an SDR and an HDR file."), 400
|
||||||
|
|
||||||
|
ignore_ig_resolution = request.form.get("ignore_ig_resolution") == "on"
|
||||||
|
|
||||||
job_id = uuid.uuid4().hex[:12]
|
job_id = uuid.uuid4().hex[:12]
|
||||||
job_dir = jobs.new_job_dir(job_id)
|
job_dir = jobs.new_job_dir(job_id)
|
||||||
|
|
||||||
items = [
|
items = [
|
||||||
_process_pair(job_dir, i, sdr_file, hdr_file)
|
_process_pair(job_dir, i, sdr_file, hdr_file, ignore_ig_resolution)
|
||||||
for i, (sdr_file, hdr_file) in enumerate(zip(sdr_files, hdr_files), start=1)
|
for i, (sdr_file, hdr_file) in enumerate(zip(sdr_files, hdr_files), start=1)
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -42,9 +42,17 @@ def probe_dimensions(path: Path) -> tuple[int, int]:
|
|||||||
raise ValueError(f"could not read image dimensions ({e})") from e
|
raise ValueError(f"could not read image dimensions ({e})") from e
|
||||||
|
|
||||||
|
|
||||||
def instagram_resolution_error(sdr_path: Path, hdr_path: Path) -> str | None:
|
def instagram_resolution_error(
|
||||||
|
sdr_path: Path, hdr_path: Path, ignore_ig_resolution: bool = False,
|
||||||
|
) -> str | None:
|
||||||
"""Returns an error message if the pair doesn't meet Instagram's feed
|
"""Returns an error message if the pair doesn't meet Instagram's feed
|
||||||
requirements, otherwise None."""
|
requirements, otherwise None.
|
||||||
|
|
||||||
|
The dimension-match check always applies: mismatched SDR/HDR
|
||||||
|
dimensions misalign the gain map regardless of what platform the
|
||||||
|
result is for. ignore_ig_resolution skips only the exact-match check
|
||||||
|
against Instagram's own feed resolutions, for results meant for
|
||||||
|
somewhere other than Instagram."""
|
||||||
try:
|
try:
|
||||||
sdr_w, sdr_h = probe_dimensions(sdr_path)
|
sdr_w, sdr_h = probe_dimensions(sdr_path)
|
||||||
hdr_w, hdr_h = probe_dimensions(hdr_path)
|
hdr_w, hdr_h = probe_dimensions(hdr_path)
|
||||||
@@ -57,7 +65,7 @@ def instagram_resolution_error(sdr_path: Path, hdr_path: Path) -> str | None:
|
|||||||
"dimensions, otherwise the gain map will misalign."
|
"dimensions, otherwise the gain map will misalign."
|
||||||
)
|
)
|
||||||
|
|
||||||
if (sdr_w, sdr_h) not in IG_RESOLUTIONS:
|
if not ignore_ig_resolution and (sdr_w, sdr_h) not in IG_RESOLUTIONS:
|
||||||
allowed = ", ".join(f"{w}x{h} ({label})" for (w, h), label in IG_RESOLUTIONS.items())
|
allowed = ", ".join(f"{w}x{h} ({label})" for (w, h), label in IG_RESOLUTIONS.items())
|
||||||
return f"Image is {sdr_w}x{sdr_h} — Instagram requires an exact match to one of: {allowed}."
|
return f"Image is {sdr_w}x{sdr_h} — Instagram requires an exact match to one of: {allowed}."
|
||||||
|
|
||||||
@@ -3,12 +3,13 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>Instagram HDR Assembler</title>
|
<title>Instameex</title>
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
<link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='img/logo.svg') }}">
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<p class="eyebrow">Instagram HDR Assembler</p>
|
<img class="logo" src="{{ url_for('static', filename='img/logo-long.svg') }}" alt="Instameex">
|
||||||
<h1>Build your HDR JPEG for Instagram</h1>
|
<h1>Build your HDR JPEG for Instagram</h1>
|
||||||
<p class="subtitle">Drop your SDR export and your HDR export from Lightroom / Camera Raw. The script does the rest — gain map, 4:2:0 subsampling, Instagram metadata injection. Add more pairs to process several photos in one go.</p>
|
<p class="subtitle">Drop your SDR export and your HDR export from Lightroom / Camera Raw. The script does the rest — gain map, 4:2:0 subsampling, Instagram metadata injection. Add more pairs to process several photos in one go.</p>
|
||||||
|
|
||||||
@@ -40,18 +41,35 @@
|
|||||||
|
|
||||||
<button type="button" class="add-duo" id="add-duo">+ Add another pair</button>
|
<button type="button" class="add-duo" id="add-duo">+ Add another pair</button>
|
||||||
|
|
||||||
|
<label class="checkbox-row">
|
||||||
|
<input type="checkbox" name="ignore_ig_resolution">
|
||||||
|
Process anyway if the resolution doesn't match Instagram's feed sizes
|
||||||
|
</label>
|
||||||
|
|
||||||
<button class="run" type="submit">Assemble the HDR</button>
|
<button class="run" type="submit">Assemble the HDR</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="footnote">
|
<p class="footnote">
|
||||||
Each pair's files must have identical dimensions, matching exactly one of Instagram's feed
|
Each pair's files must have identical dimensions. By default that must also match one of
|
||||||
resolutions: <strong>1080x1080</strong> (1:1), <strong>1080x1350</strong> (4:5), or
|
Instagram's feed resolutions exactly: <strong>1080x1080</strong> (1:1),
|
||||||
<strong>1080x566</strong> (1.91:1). Assembling more than one pair downloads as a <code>.zip</code>.
|
<strong>1080x1350</strong> (4:5), or <strong>1080x566</strong> (1.91:1); check the box above
|
||||||
HDR can be a regular gain-map JPEG or Lightroom's 32-bit float linear HDR TIFF export; a
|
to skip that check for results meant for somewhere other than Instagram. Assembling more
|
||||||
TIFF HDR file gives a more precise result than the JPEG gain-map route.
|
than one pair downloads as a <code>.zip</code>. HDR can be a regular gain-map JPEG or
|
||||||
|
Lightroom's 32-bit float linear HDR TIFF export; a TIFF HDR file gives a more precise result
|
||||||
|
than the JPEG gain-map route.
|
||||||
</p>
|
</p>
|
||||||
<p class="version">Instagram HDR Assembler v{{ app_version }}</p>
|
<p class="version">Instameex v{{ app_version }}</p>
|
||||||
|
<div class="repo-links">
|
||||||
|
<a href="https://git.djeex.fr/Djeex/instameex" target="_blank" rel="noopener">
|
||||||
|
<span class="icon"><img src="{{ url_for('static', filename='img/gitea.svg') }}" alt=""></span>
|
||||||
|
Gitea
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/Djeex/instameex" target="_blank" rel="noopener">
|
||||||
|
<span class="icon"><img src="{{ url_for('static', filename='img/github.svg') }}" alt=""></span>
|
||||||
|
GitHub
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -3,8 +3,9 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>Instagram HDR Assembler — Result</title>
|
<title>Instameex - Result</title>
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
<link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='img/logo.svg') }}">
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
@@ -43,7 +44,17 @@
|
|||||||
Remember to upload from a desktop browser (up-to-date Chrome) afterwards, choosing
|
Remember to upload from a desktop browser (up-to-date Chrome) afterwards, choosing
|
||||||
<code>Original</code> as the crop — not the default 1:1 crop.
|
<code>Original</code> as the crop — not the default 1:1 crop.
|
||||||
</p>
|
</p>
|
||||||
<p class="version">Instagram HDR Assembler v{{ app_version }}</p>
|
<p class="version">Instameex v{{ app_version }}</p>
|
||||||
|
<div class="repo-links">
|
||||||
|
<a href="https://git.djeex.fr/Djeex/instameex" target="_blank" rel="noopener">
|
||||||
|
<span class="icon"><img src="{{ url_for('static', filename='img/gitea.svg') }}" alt=""></span>
|
||||||
|
Gitea
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/Djeex/instameex" target="_blank" rel="noopener">
|
||||||
|
<span class="icon"><img src="{{ url_for('static', filename='img/github.svg') }}" alt=""></span>
|
||||||
|
GitHub
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
Reference in New Issue
Block a user