HiveGrid Social: Design-Library und Post-Generator
Autarke Werkbank für Social-Grafiken: Slides als HTML, Bearbeitung im Canvas, Export als Instagram-taugliche PNGs. - Assets nach brand/partner/mockups/screens sortiert, Karteileichen entfernt - G-Marke als eigene Datei statt nur als Polygon im Code - Partner-Logos und Geerkens-Logo ergänzt - Keine externen Referenzen: Schrift, Bibliotheken und Bilder liegen lokal
No files matched your search
@@ -0,0 +1 @@
|
||||
.DS_Store
|
||||
@@ -0,0 +1,67 @@
|
||||
# HiveGrid Social — Design-Library & Post-Generator
|
||||
|
||||
Eigenständige Werkbank für HiveGrid-Social-Grafiken. Slides werden im Browser
|
||||
als HTML gebaut, im Canvas bearbeitet und pixelgenau als Instagram-taugliche
|
||||
PNGs exportiert.
|
||||
|
||||
**Das Repo ist bewusst autark.** Keine CDN-Verweise, keine Google-Fonts zur
|
||||
Laufzeit, keine Pfade in andere Repos. Alles, was ein Post braucht — Schrift,
|
||||
Logos, Mockups, Screenshots, Bibliotheken — liegt hier.
|
||||
|
||||
## Starten
|
||||
|
||||
```sh
|
||||
./serve.sh # http://127.0.0.1:4321
|
||||
```
|
||||
|
||||
Downloads landen direkt in `~/Downloads`, ohne Browser-Dialog.
|
||||
|
||||
## Projekte
|
||||
|
||||
| Projekt | Datei | Inhalt |
|
||||
|---|---|---|
|
||||
| Post 1 · Lösung | `post1.html` | Carousel, 5 Slides |
|
||||
| Post 2 · Statement | `index.html` | Einzelbild |
|
||||
|
||||
Oben im Bedienfeld wechselt man zwischen ihnen. **Jedes Projekt speichert
|
||||
seinen Stand** — Einstellungen, Layouts, Muster und alle Verschiebungen aus dem
|
||||
Canvas überleben einen Reload. „Projekt zurücksetzen" wirft ihn weg.
|
||||
|
||||
## Bedienung
|
||||
|
||||
| Taste | Wirkung |
|
||||
|---|---|
|
||||
| `←` `→` | Slide wechseln (im Bearbeiten-Modus: Element verschieben) |
|
||||
| `E` | Bearbeiten an/aus |
|
||||
| `W` | Streifen ⇄ Einzelansicht |
|
||||
| `V` | Variante hell/dunkel |
|
||||
| `L` `M` `I` | Layout · Muster · Intensität |
|
||||
| `C` `G` `T` | Durchgehend · Tag-Ausrückung · Perspektive |
|
||||
| `D` `A` | Slide laden · alle als ZIP |
|
||||
|
||||
Im Bearbeiten-Modus lassen sich Elemente ziehen, mit den Pfeiltasten
|
||||
pixelweise schieben (`⇧` = 10 px) und über `A−`/`A+` in der Größe ändern.
|
||||
|
||||
## Aufbau
|
||||
|
||||
```
|
||||
assets/
|
||||
├── brand/ Wortmarke hell/dunkel, G-Marke (SVG + PNG), Wabe, Geerkens
|
||||
├── partner/ DATEV, KWP, Google Calendar, Google Drive, MS Outlook, MS Teams
|
||||
├── mockups/ Geräte-Mockups (Display ausgeknockt, Screenshot liegt dahinter)
|
||||
├── screens/ Software-Screenshots
|
||||
└── fonts/ Manrope 400–800, lokal
|
||||
vendor/ html-to-image, JSZip
|
||||
serve.py Statischer Server + /save-Endpunkt
|
||||
rules.md Gestaltungsregeln, Farbwerte, Export-Wissen
|
||||
```
|
||||
|
||||
## Regeln
|
||||
|
||||
**`rules.md` ist die Arbeitsgrundlage.** Farbwerte, Geometrie, Typografie,
|
||||
Layout-Template, Instagram-Export und die Liste der Fallen, in die wir schon
|
||||
getappt sind. Vor jeder Änderung an der Gestaltung dort nachsehen.
|
||||
|
||||
Marken-Werte stammen aus der Website (`HiveGrid-Website`) und sind hier
|
||||
**gespiegelt**, nicht verlinkt. Ändert sich dort etwas, muss es hier
|
||||
nachgezogen werden.
|
||||
|
After Width: | Height: | Size: 266 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384.5 332.9" role="img" aria-label="HiveGrid">
|
||||
<polygon fill="#F1B729" points="384.5,166.5 336.4,83.2 192.2,166.5 288.3,166.5 240.3,249.7 144.2,249.7 96.1,166.5 144.2,83.2 336.4,83.2 298,16.8 288.3,0 96.1,0 0,166.5 96.1,332.9 288.3,332.9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 303 B |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 49 KiB |
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 29.5.0, SVG Export Plug-In . SVG Version: 9.03 Build 0) -->
|
||||
<svg version="1.0" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 1900 500" style="enable-background:new 0 0 1900 500;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{clip-path:url(#SVGID_00000085213544704903965630000016245828043251886486_);}
|
||||
.st1{fill:#F9F9F9;}
|
||||
.st2{clip-path:url(#SVGID_00000038397202409254422500000000715208381988780936_);}
|
||||
.st3{fill:#F1B729;}
|
||||
</style>
|
||||
<g id="Ebene_1_00000147200394007143738050000007847843949640695430_">
|
||||
<g transform="matrix(1, 0, 0, 1, 0, 32)">
|
||||
<g>
|
||||
<g>
|
||||
<defs>
|
||||
<rect id="SVGID_1_" y="10.7" width="941.3" height="421.3"/>
|
||||
</defs>
|
||||
<clipPath id="SVGID_00000173870619096606741830000001135887134242725528_">
|
||||
<use xlink:href="#SVGID_1_" style="overflow:visible;"/>
|
||||
</clipPath>
|
||||
<g style="clip-path:url(#SVGID_00000173870619096606741830000001135887134242725528_);">
|
||||
<g>
|
||||
<g transform="translate(-1.971007, 242.87892)">
|
||||
<g>
|
||||
<path class="st1" d="M28.6-164.7h58.6v102.5H219v-102.5h58.6V91.6H219V-11H87.2V91.6H28.6V-164.7z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g transform="translate(221.853252, 242.87892)">
|
||||
<g>
|
||||
<path class="st1" d="M103.2-164.7h58.6V91.6h-58.6C103.2,91.6,103.2-164.7,103.2-164.7z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g transform="translate(302.86895, 242.87892)">
|
||||
<g>
|
||||
<path class="st1" d="M110.1-135.4v-29.3H165l76.9,199.6h3.7l76.9-199.6h54.9v29.3l-95.2,227h-76.9L110.1-135.4z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<defs>
|
||||
<rect id="SVGID_00000050649768837086059390000000432696666009736893_" x="1186.7" y="42.7" width="704" height="421.3"/>
|
||||
</defs>
|
||||
<clipPath id="SVGID_00000060012912674953308700000002027295538902951813_">
|
||||
<use xlink:href="#SVGID_00000050649768837086059390000000432696666009736893_" style="overflow:visible;"/>
|
||||
</clipPath>
|
||||
<g style="clip-path:url(#SVGID_00000060012912674953308700000002027295538902951813_);">
|
||||
<g>
|
||||
<g transform="translate(223.06958, 242.87892)">
|
||||
<g>
|
||||
<path class="st1" d="M1290.3-132.9h58.6v256.3h-58.6V-132.9z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g transform="translate(304.085278, 242.87892)">
|
||||
<g>
|
||||
<path class="st1" d="M1317.3-132.9h188.6c18.3,0,32.9,5.5,43.9,16.5c11,11,16.5,25.6,16.5,43.9V63c0,18.3-5.5,33-16.5,43.9
|
||||
s-25.6,16.5-43.9,16.5h-188.6V-132.9z M1483.9,72.2c15.9,0,23.8-7.9,23.8-23.8V-57.8c0-15.9-7.9-23.8-23.8-23.8h-108V72.2
|
||||
H1483.9z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Ebene_5">
|
||||
<path class="st1" d="M1418.2,271.1c9-1.7,17.2-4.8,24.5-9.1c6.3-3.9,12.1-9.6,17.2-17c5.1-7.5,7.7-17.6,7.7-30.6v-43.9
|
||||
c0-18.3-5.5-32.9-16.5-43.9c-11-11-25.6-16.5-43.9-16.5h-184.9v13.5l66.1,114.6l-66.1,114.2v14h58.6V264l59.3-0.1l79.9,102.4h53.1
|
||||
V337L1418.2,271.1z M1409,199.7c0,15.9-8.8,13-24.6,13h-103.5v-51.5h104.3c15.9,0,23.8,7.9,23.8,23.8V199.7z"/>
|
||||
</g>
|
||||
<g id="Ebene_4">
|
||||
<polygon class="st1" points="907.1,315.2 766.4,315.2 766.4,263.9 877.5,263.9 862.3,237.6 876.7,212.7 766.4,212.7 766.4,161.4
|
||||
906.3,161.4 927.5,124.9 927.5,110.2 707.8,110.2 707.8,366.5 927.5,366.5 927.5,350.6 "/>
|
||||
</g>
|
||||
<g id="Ebene_2_00000103265633317154261300000008405163556884147082_">
|
||||
<g>
|
||||
<g>
|
||||
<polygon class="st3" points="1266.6,237.2 1218.5,153.9 1074.3,237.2 1170.4,237.2 1122.4,320.4 1026.3,320.4 978.2,237.2
|
||||
1026.3,153.9 1218.5,153.9 1180.1,87.5 1170.4,70.7 978.2,70.7 882.1,237.2 978.2,403.6 1170.4,403.6 "/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 29.5.0, SVG Export Plug-In . SVG Version: 9.03 Build 0) -->
|
||||
<svg version="1.0" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 1900 500" style="enable-background:new 0 0 1900 500;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{clip-path:url(#SVGID_00000092424140864995002100000002551967465525493131_);}
|
||||
.st1{fill:#231F20;}
|
||||
.st2{clip-path:url(#SVGID_00000050631655892142403640000003269334890553695391_);}
|
||||
.st3{fill:#F1B729;}
|
||||
</style>
|
||||
<g id="Ebene_1_00000147200394007143738050000007847843949640695430_">
|
||||
<g transform="matrix(1, 0, 0, 1, 0, 32)">
|
||||
<g>
|
||||
<g>
|
||||
<defs>
|
||||
<rect id="SVGID_1_" y="10.7" width="941.3" height="421.3"/>
|
||||
</defs>
|
||||
<clipPath id="SVGID_00000021828690431161971110000008666306399099804576_">
|
||||
<use xlink:href="#SVGID_1_" style="overflow:visible;"/>
|
||||
</clipPath>
|
||||
<g style="clip-path:url(#SVGID_00000021828690431161971110000008666306399099804576_);">
|
||||
<g>
|
||||
<g transform="translate(-1.971007, 242.87892)">
|
||||
<g>
|
||||
<path class="st1" d="M28.6-164.7h58.6v102.5H219v-102.5h58.6V91.6H219V-11H87.2V91.6H28.6V-164.7z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g transform="translate(221.853252, 242.87892)">
|
||||
<g>
|
||||
<path class="st1" d="M103.2-164.7h58.6V91.6h-58.6C103.2,91.6,103.2-164.7,103.2-164.7z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g transform="translate(302.86895, 242.87892)">
|
||||
<g>
|
||||
<path class="st1" d="M110.1-135.4v-29.3H165l76.9,199.6h3.7l76.9-199.6h54.9v29.3l-95.2,227h-76.9L110.1-135.4z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<defs>
|
||||
<rect id="SVGID_00000029025352032733430810000007867561513642331544_" x="1186.7" y="42.7" width="704" height="421.3"/>
|
||||
</defs>
|
||||
<clipPath id="SVGID_00000132086783084342479510000013717535083693061769_">
|
||||
<use xlink:href="#SVGID_00000029025352032733430810000007867561513642331544_" style="overflow:visible;"/>
|
||||
</clipPath>
|
||||
<g style="clip-path:url(#SVGID_00000132086783084342479510000013717535083693061769_);">
|
||||
<g>
|
||||
<g transform="translate(223.06958, 242.87892)">
|
||||
<g>
|
||||
<path class="st1" d="M1290.3-132.9h58.6v256.3h-58.6V-132.9z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g transform="translate(304.085278, 242.87892)">
|
||||
<g>
|
||||
<path class="st1" d="M1317.3-132.9h188.6c18.3,0,32.9,5.5,43.9,16.5c11,11,16.5,25.6,16.5,43.9V63c0,18.3-5.5,33-16.5,43.9
|
||||
s-25.6,16.5-43.9,16.5h-188.6V-132.9z M1483.9,72.2c15.9,0,23.8-7.9,23.8-23.8V-57.8c0-15.9-7.9-23.8-23.8-23.8h-108V72.2
|
||||
H1483.9z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Ebene_5">
|
||||
<path class="st1" d="M1418.2,271.1c9-1.7,17.2-4.8,24.5-9.1c6.3-3.9,12.1-9.6,17.2-17c5.1-7.5,7.7-17.6,7.7-30.6v-43.9
|
||||
c0-18.3-5.5-32.9-16.5-43.9c-11-11-25.6-16.5-43.9-16.5h-184.9v13.5l66.1,114.6l-66.1,114.2v14h58.6V264l59.3-0.1l79.9,102.4h53.1
|
||||
V337L1418.2,271.1z M1409,199.7c0,15.9-8.8,13-24.6,13h-103.5v-51.5h104.3c15.9,0,23.8,7.9,23.8,23.8V199.7z"/>
|
||||
</g>
|
||||
<g id="Ebene_4">
|
||||
<polygon class="st1" points="907.1,315.2 766.4,315.2 766.4,263.9 877.5,263.9 862.3,237.6 876.7,212.7 766.4,212.7 766.4,161.4
|
||||
906.3,161.4 927.5,124.9 927.5,110.2 707.8,110.2 707.8,366.5 927.5,366.5 927.5,350.6 "/>
|
||||
</g>
|
||||
<g id="Ebene_2_00000103265633317154261300000008405163556884147082_">
|
||||
<g>
|
||||
<g>
|
||||
<polygon class="st3" points="1266.6,237.2 1218.5,153.9 1074.3,237.2 1170.4,237.2 1122.4,320.4 1026.3,320.4 978.2,237.2
|
||||
1026.3,153.9 1218.5,153.9 1180.1,87.5 1170.4,70.7 978.2,70.7 882.1,237.2 978.2,403.6 1170.4,403.6 "/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
@@ -0,0 +1,270 @@
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Manrope';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(./xn7gYHE41ni1AdIRggqxSvfedN62Zw.woff2) format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Manrope';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(./xn7gYHE41ni1AdIRggOxSvfedN62Zw.woff2) format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Manrope';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(./xn7gYHE41ni1AdIRggSxSvfedN62Zw.woff2) format('woff2');
|
||||
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Manrope';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(./xn7gYHE41ni1AdIRggixSvfedN62Zw.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Manrope';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(./xn7gYHE41ni1AdIRggmxSvfedN62Zw.woff2) format('woff2');
|
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Manrope';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(./xn7gYHE41ni1AdIRggexSvfedN4.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Manrope';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url(./xn7gYHE41ni1AdIRggqxSvfedN62Zw.woff2) format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Manrope';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url(./xn7gYHE41ni1AdIRggOxSvfedN62Zw.woff2) format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Manrope';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url(./xn7gYHE41ni1AdIRggSxSvfedN62Zw.woff2) format('woff2');
|
||||
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Manrope';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url(./xn7gYHE41ni1AdIRggixSvfedN62Zw.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Manrope';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url(./xn7gYHE41ni1AdIRggmxSvfedN62Zw.woff2) format('woff2');
|
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Manrope';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url(./xn7gYHE41ni1AdIRggexSvfedN4.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Manrope';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url(./xn7gYHE41ni1AdIRggqxSvfedN62Zw.woff2) format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Manrope';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url(./xn7gYHE41ni1AdIRggOxSvfedN62Zw.woff2) format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Manrope';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url(./xn7gYHE41ni1AdIRggSxSvfedN62Zw.woff2) format('woff2');
|
||||
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Manrope';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url(./xn7gYHE41ni1AdIRggixSvfedN62Zw.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Manrope';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url(./xn7gYHE41ni1AdIRggmxSvfedN62Zw.woff2) format('woff2');
|
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Manrope';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url(./xn7gYHE41ni1AdIRggexSvfedN4.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Manrope';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(./xn7gYHE41ni1AdIRggqxSvfedN62Zw.woff2) format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Manrope';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(./xn7gYHE41ni1AdIRggOxSvfedN62Zw.woff2) format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Manrope';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(./xn7gYHE41ni1AdIRggSxSvfedN62Zw.woff2) format('woff2');
|
||||
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Manrope';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(./xn7gYHE41ni1AdIRggixSvfedN62Zw.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Manrope';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(./xn7gYHE41ni1AdIRggmxSvfedN62Zw.woff2) format('woff2');
|
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Manrope';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(./xn7gYHE41ni1AdIRggexSvfedN4.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Manrope';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url(./xn7gYHE41ni1AdIRggqxSvfedN62Zw.woff2) format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Manrope';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url(./xn7gYHE41ni1AdIRggOxSvfedN62Zw.woff2) format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Manrope';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url(./xn7gYHE41ni1AdIRggSxSvfedN62Zw.woff2) format('woff2');
|
||||
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Manrope';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url(./xn7gYHE41ni1AdIRggixSvfedN62Zw.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Manrope';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url(./xn7gYHE41ni1AdIRggmxSvfedN62Zw.woff2) format('woff2');
|
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Manrope';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url(./xn7gYHE41ni1AdIRggexSvfedN4.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Livello_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
|
||||
<g>
|
||||
<g transform="translate(3.75 3.75)">
|
||||
<path fill="#FFFFFF" d="M148.882,43.618l-47.368-5.263l-57.895,5.263L38.355,96.25l5.263,52.632l52.632,6.579l52.632-6.579
|
||||
l5.263-53.947L148.882,43.618z"/>
|
||||
<path fill="#1A73E8" d="M65.211,125.276c-3.934-2.658-6.658-6.539-8.145-11.671l9.132-3.763c0.829,3.158,2.276,5.605,4.342,7.342
|
||||
c2.053,1.737,4.553,2.592,7.474,2.592c2.987,0,5.553-0.908,7.697-2.724s3.224-4.132,3.224-6.934c0-2.868-1.132-5.211-3.395-7.026
|
||||
s-5.105-2.724-8.5-2.724h-5.276v-9.039H76.5c2.921,0,5.382-0.789,7.382-2.368c2-1.579,3-3.737,3-6.487
|
||||
c0-2.447-0.895-4.395-2.684-5.855s-4.053-2.197-6.803-2.197c-2.684,0-4.816,0.711-6.395,2.145s-2.724,3.197-3.447,5.276
|
||||
l-9.039-3.763c1.197-3.395,3.395-6.395,6.618-8.987c3.224-2.592,7.342-3.895,12.342-3.895c3.697,0,7.026,0.711,9.974,2.145
|
||||
c2.947,1.434,5.263,3.421,6.934,5.947c1.671,2.539,2.5,5.382,2.5,8.539c0,3.224-0.776,5.947-2.329,8.184
|
||||
c-1.553,2.237-3.461,3.947-5.724,5.145v0.539c2.987,1.25,5.421,3.158,7.342,5.724c1.908,2.566,2.868,5.632,2.868,9.211
|
||||
s-0.908,6.776-2.724,9.579c-1.816,2.803-4.329,5.013-7.513,6.618c-3.197,1.605-6.789,2.421-10.776,2.421
|
||||
C73.408,129.263,69.145,127.934,65.211,125.276z"/>
|
||||
<path fill="#1A73E8" d="M121.25,79.961l-9.974,7.25l-5.013-7.605l17.987-12.974h6.895v61.197h-9.895L121.25,79.961z"/>
|
||||
<path fill="#EA4335" d="M148.882,196.25l47.368-47.368l-23.684-10.526l-23.684,10.526l-10.526,23.684L148.882,196.25z"/>
|
||||
<path fill="#34A853" d="M33.092,172.566l10.526,23.684h105.263v-47.368H43.618L33.092,172.566z"/>
|
||||
<path fill="#4285F4" d="M12.039-3.75C3.316-3.75-3.75,3.316-3.75,12.039v136.842l23.684,10.526l23.684-10.526V43.618h105.263
|
||||
l10.526-23.684L148.882-3.75H12.039z"/>
|
||||
<path fill="#188038" d="M-3.75,148.882v31.579c0,8.724,7.066,15.789,15.789,15.789h31.579v-47.368H-3.75z"/>
|
||||
<path fill="#FBBC04" d="M148.882,43.618v105.263h47.368V43.618l-23.684-10.526L148.882,43.618z"/>
|
||||
<path fill="#1967D2" d="M196.25,43.618V12.039c0-8.724-7.066-15.789-15.789-15.789h-31.579v47.368H196.25z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,8 @@
|
||||
<svg viewBox="0 0 87.3 78" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m6.6 66.85 3.85 6.65c.8 1.4 1.95 2.5 3.3 3.3l13.75-23.8h-27.5c0 1.55.4 3.1 1.2 4.5z" fill="#0066da"/>
|
||||
<path d="m43.65 25-13.75-23.8c-1.35.8-2.5 1.9-3.3 3.3l-25.4 44a9.06 9.06 0 0 0 -1.2 4.5h27.5z" fill="#00ac47"/>
|
||||
<path d="m73.55 76.8c1.35-.8 2.5-1.9 3.3-3.3l1.6-2.75 7.65-13.25c.8-1.4 1.2-2.95 1.2-4.5h-27.502l5.852 11.5z" fill="#ea4335"/>
|
||||
<path d="m43.65 25 13.75-23.8c-1.35-.8-2.9-1.2-4.5-1.2h-18.5c-1.6 0-3.15.45-4.5 1.2z" fill="#00832d"/>
|
||||
<path d="m59.8 53h-32.3l-13.75 23.8c1.35.8 2.9 1.2 4.5 1.2h50.8c1.6 0 3.15-.45 4.5-1.2z" fill="#2684fc"/>
|
||||
<path d="m73.4 26.5-12.7-22c-.8-1.4-1.95-2.5-3.3-3.3l-13.75 23.8 16.15 28h27.45c0-1.55-.4-3.1-1.2-4.5z" fill="#ffba00"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 755 B |
|
After Width: | Height: | Size: 60 KiB |
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Livello_1" xmlns:x="http://ns.adobe.com/Extensibility/1.0/" xmlns:i="http://ns.adobe.com/AdobeIllustrator/10.0/" xmlns:graph="http://ns.adobe.com/Graphs/1.0/" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1831.085 1703.335" enable-background="new 0 0 1831.085 1703.335" xml:space="preserve">
|
||||
<path fill="#0A2767" d="M1831.083,894.25c0.1-14.318-7.298-27.644-19.503-35.131h-0.213l-0.767-0.426l-634.492-375.585 c-2.74-1.851-5.583-3.543-8.517-5.067c-24.498-12.639-53.599-12.639-78.098,0c-2.934,1.525-5.777,3.216-8.517,5.067L446.486,858.693 l-0.766,0.426c-19.392,12.059-25.337,37.556-13.278,56.948c3.553,5.714,8.447,10.474,14.257,13.868l634.492,375.585 c2.749,1.835,5.592,3.527,8.517,5.068c24.498,12.639,53.599,12.639,78.098,0c2.925-1.541,5.767-3.232,8.517-5.068l634.492-375.585 C1823.49,922.545,1831.228,908.923,1831.083,894.25z"/>
|
||||
<path fill="#0364B8" d="M520.453,643.477h416.38v381.674h-416.38V643.477z M1745.917,255.5V80.908 c1-43.652-33.552-79.862-77.203-80.908H588.204C544.552,1.046,510,37.256,511,80.908V255.5l638.75,170.333L1745.917,255.5z"/>
|
||||
<path fill="#0078D4" d="M511,255.5h425.833v383.25H511V255.5z"/>
|
||||
<path fill="#28A8EA" d="M1362.667,255.5H936.833v383.25L1362.667,1022h383.25V638.75L1362.667,255.5z"/>
|
||||
<path fill="#0078D4" d="M936.833,638.75h425.833V1022H936.833V638.75z"/>
|
||||
<path fill="#0364B8" d="M936.833,1022h425.833v383.25H936.833V1022z"/>
|
||||
<path fill="#14447D" d="M520.453,1025.151h416.38v346.969h-416.38V1025.151z"/>
|
||||
<path fill="#0078D4" d="M1362.667,1022h383.25v383.25h-383.25V1022z"/>
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="1128.4584" y1="811.0833" x2="1128.4584" y2="1.9982" gradientTransform="matrix(1 0 0 -1 0 1705.3334)">
|
||||
<stop offset="0" style="stop-color:#35B8F1"/>
|
||||
<stop offset="1" style="stop-color:#28A8EA"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_1_)" d="M1811.58,927.593l-0.809,0.426l-634.492,356.848c-2.768,1.703-5.578,3.321-8.517,4.769 c-10.777,5.132-22.481,8.029-34.407,8.517l-34.663-20.27c-2.929-1.47-5.773-3.105-8.517-4.897L447.167,906.003h-0.298 l-21.036-11.753v722.384c0.328,48.196,39.653,87.006,87.849,86.7h1230.914c0.724,0,1.363-0.341,2.129-0.341 c10.18-0.651,20.216-2.745,29.808-6.217c4.145-1.756,8.146-3.835,11.966-6.217c2.853-1.618,7.75-5.152,7.75-5.152 c21.814-16.142,34.726-41.635,34.833-68.772V894.25C1831.068,908.067,1823.616,920.807,1811.58,927.593z"/>
|
||||
<path opacity="0.5" fill="#0A2767" enable-background="new " d="M1797.017,891.397v44.287l-663.448,456.791L446.699,906.301 c0-0.235-0.191-0.426-0.426-0.426l0,0l-63.023-37.899v-31.938l25.976-0.426l54.932,31.512l1.277,0.426l4.684,2.981 c0,0,645.563,368.346,647.267,369.197l24.698,14.478c2.129-0.852,4.258-1.703,6.813-2.555 c1.278-0.852,640.879-360.681,640.879-360.681L1797.017,891.397z"/>
|
||||
<path fill="#1490DF" d="M1811.58,927.593l-0.809,0.468l-634.492,356.848c-2.768,1.703-5.578,3.321-8.517,4.769 c-24.641,12.038-53.457,12.038-78.098,0c-2.918-1.445-5.76-3.037-8.517-4.769L446.657,928.061l-0.766-0.468 c-12.25-6.642-19.93-19.409-20.057-33.343v722.384c0.305,48.188,39.616,87.004,87.803,86.7c0.001,0,0.002,0,0.004,0h1229.636 c48.188,0.307,87.5-38.509,87.807-86.696c0-0.001,0-0.002,0-0.004V894.25C1831.068,908.067,1823.616,920.807,1811.58,927.593z"/>
|
||||
<path opacity="0.1" enable-background="new " d="M1185.52,1279.629l-9.496,5.323c-2.752,1.752-5.595,3.359-8.517,4.812 c-10.462,5.135-21.838,8.146-33.47,8.857l241.405,285.479l421.107,101.476c11.539-8.716,20.717-20.178,26.7-33.343L1185.52,1279.629 z"/>
|
||||
<path opacity="0.05" enable-background="new " d="M1228.529,1255.442l-52.505,29.51c-2.752,1.752-5.595,3.359-8.517,4.812 c-10.462,5.135-21.838,8.146-33.47,8.857l113.101,311.838l549.538,74.989c21.649-16.254,34.394-41.743,34.407-68.815v-9.326 L1228.529,1255.442z"/>
|
||||
<path fill="#28A8EA" d="M514.833,1703.333h1228.316c18.901,0.096,37.335-5.874,52.59-17.033l-697.089-408.331 c-2.929-1.47-5.773-3.105-8.517-4.897L447.125,906.088h-0.298l-20.993-11.838v719.914 C425.786,1663.364,465.632,1703.286,514.833,1703.333C514.832,1703.333,514.832,1703.333,514.833,1703.333z"/>
|
||||
<path opacity="0.1" enable-background="new " d="M1022,418.722v908.303c-0.076,31.846-19.44,60.471-48.971,72.392 c-9.148,3.931-19,5.96-28.957,5.962H425.833V383.25H511v-42.583h433.073C987.092,340.83,1021.907,375.702,1022,418.722z"/>
|
||||
<path opacity="0.2" enable-background="new " d="M979.417,461.305v908.302c0.107,10.287-2.074,20.469-6.388,29.808 c-11.826,29.149-40.083,48.273-71.54,48.417H425.833V383.25h475.656c12.356-0.124,24.533,2.958,35.344,8.943 C962.937,405.344,979.407,432.076,979.417,461.305z"/>
|
||||
<path opacity="0.2" enable-background="new " d="M979.417,461.305v823.136c-0.208,43-34.928,77.853-77.927,78.225H425.833V383.25 h475.656c12.356-0.124,24.533,2.958,35.344,8.943C962.937,405.344,979.407,432.076,979.417,461.305z"/>
|
||||
<path opacity="0.2" enable-background="new " d="M936.833,461.305v823.136c-0.046,43.067-34.861,78.015-77.927,78.225H425.833 V383.25h433.072c43.062,0.023,77.951,34.951,77.927,78.013C936.833,461.277,936.833,461.291,936.833,461.305z"/>
|
||||
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="162.7469" y1="1383.0741" x2="774.0864" y2="324.2592" gradientTransform="matrix(1 0 0 -1 0 1705.3334)">
|
||||
<stop offset="0" style="stop-color:#1784D9"/>
|
||||
<stop offset="0.5" style="stop-color:#107AD5"/>
|
||||
<stop offset="1" style="stop-color:#0A63C9"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_2_)" d="M78.055,383.25h780.723c43.109,0,78.055,34.947,78.055,78.055v780.723 c0,43.109-34.946,78.055-78.055,78.055H78.055c-43.109,0-78.055-34.947-78.055-78.055V461.305 C0,418.197,34.947,383.25,78.055,383.25z"/>
|
||||
<path fill="#FFFFFF" d="M243.96,710.631c19.238-40.988,50.29-75.289,89.17-98.495c43.057-24.651,92.081-36.94,141.675-35.515 c45.965-0.997,91.321,10.655,131.114,33.683c37.414,22.312,67.547,55.004,86.742,94.109c20.904,43.09,31.322,90.512,30.405,138.396 c1.013,50.043-9.706,99.628-31.299,144.783c-19.652,40.503-50.741,74.36-89.425,97.388c-41.327,23.734-88.367,35.692-136.011,34.578 c-46.947,1.133-93.303-10.651-134.01-34.067c-37.738-22.341-68.249-55.07-87.892-94.28c-21.028-42.467-31.57-89.355-30.745-136.735 C212.808,804.859,223.158,755.686,243.96,710.631z M339.006,941.858c10.257,25.912,27.651,48.385,50.163,64.812 c22.93,16.026,50.387,24.294,78.353,23.591c29.783,1.178,59.14-7.372,83.634-24.358c22.227-16.375,39.164-38.909,48.715-64.812 c10.677-28.928,15.946-59.572,15.543-90.404c0.33-31.127-4.623-62.084-14.649-91.554c-8.855-26.607-25.246-50.069-47.182-67.537 c-23.88-17.79-53.158-26.813-82.91-25.55c-28.572-0.74-56.644,7.593-80.184,23.804c-22.893,16.496-40.617,39.168-51.1,65.365 c-23.255,60.049-23.376,126.595-0.341,186.728L339.006,941.858z"/>
|
||||
<path fill="#50D9FF" d="M1362.667,255.5h383.25v383.25h-383.25V255.5z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.9 KiB |
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2228.833 2073.333">
|
||||
<path fill="#5059C9" d="M1554.637,777.5h575.713c54.391,0,98.483,44.092,98.483,98.483c0,0,0,0,0,0v524.398 c0,199.901-162.051,361.952-361.952,361.952h0h-1.711c-199.901,0.028-361.975-162-362.004-361.901c0-0.017,0-0.034,0-0.052V828.971 C1503.167,800.544,1526.211,777.5,1554.637,777.5L1554.637,777.5z"/>
|
||||
<circle fill="#5059C9" cx="1943.75" cy="440.583" r="233.25"/>
|
||||
<circle fill="#7B83EB" cx="1218.083" cy="336.917" r="336.917"/>
|
||||
<path fill="#7B83EB" d="M1667.323,777.5H717.01c-53.743,1.33-96.257,45.931-95.01,99.676v598.105 c-7.505,322.519,247.657,590.16,570.167,598.053c322.51-7.893,577.671-275.534,570.167-598.053V877.176 C1763.579,823.431,1721.066,778.83,1667.323,777.5z"/>
|
||||
<path opacity=".1" d="M1244,777.5v838.145c-0.258,38.435-23.549,72.964-59.09,87.598 c-11.316,4.787-23.478,7.254-35.765,7.257H667.613c-6.738-17.105-12.958-34.21-18.142-51.833 c-18.144-59.477-27.402-121.307-27.472-183.49V877.02c-1.246-53.659,41.198-98.19,94.855-99.52H1244z"/>
|
||||
<path opacity=".2" d="M1192.167,777.5v889.978c-0.002,12.287-2.47,24.449-7.257,35.765 c-14.634,35.541-49.163,58.833-87.598,59.09H691.975c-8.812-17.105-17.105-34.21-24.362-51.833 c-7.257-17.623-12.958-34.21-18.142-51.833c-18.144-59.476-27.402-121.307-27.472-183.49V877.02 c-1.246-53.659,41.198-98.19,94.855-99.52H1192.167z"/>
|
||||
<path opacity=".2" d="M1192.167,777.5v786.312c-0.395,52.223-42.632,94.46-94.855,94.855h-447.84 c-18.144-59.476-27.402-121.307-27.472-183.49V877.02c-1.246-53.659,41.198-98.19,94.855-99.52H1192.167z"/>
|
||||
<path opacity=".2" d="M1140.333,777.5v786.312c-0.395,52.223-42.632,94.46-94.855,94.855H649.472 c-18.144-59.476-27.402-121.307-27.472-183.49V877.02c-1.246-53.659,41.198-98.19,94.855-99.52H1140.333z"/>
|
||||
<path opacity=".1" d="M1244,509.522v163.275c-8.812,0.518-17.105,1.037-25.917,1.037 c-8.812,0-17.105-0.518-25.917-1.037c-17.496-1.161-34.848-3.937-51.833-8.293c-104.963-24.857-191.679-98.469-233.25-198.003 c-7.153-16.715-12.706-34.071-16.587-51.833h258.648C1201.449,414.866,1243.801,457.217,1244,509.522z"/>
|
||||
<path opacity=".2" d="M1192.167,561.355v111.442c-17.496-1.161-34.848-3.937-51.833-8.293 c-104.963-24.857-191.679-98.469-233.25-198.003h190.228C1149.616,466.699,1191.968,509.051,1192.167,561.355z"/>
|
||||
<path opacity=".2" d="M1192.167,561.355v111.442c-17.496-1.161-34.848-3.937-51.833-8.293 c-104.963-24.857-191.679-98.469-233.25-198.003h190.228C1149.616,466.699,1191.968,509.051,1192.167,561.355z"/>
|
||||
<path opacity=".2" d="M1140.333,561.355v103.148c-104.963-24.857-191.679-98.469-233.25-198.003 h138.395C1097.783,466.699,1140.134,509.051,1140.333,561.355z"/>
|
||||
<linearGradient id="a" gradientUnits="userSpaceOnUse" x1="198.099" y1="1683.0726" x2="942.2344" y2="394.2607" gradientTransform="matrix(1 0 0 -1 0 2075.3333)">
|
||||
<stop offset="0" stop-color="#5a62c3"/>
|
||||
<stop offset=".5" stop-color="#4d55bd"/>
|
||||
<stop offset="1" stop-color="#3940ab"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#a)" d="M95.01,466.5h950.312c52.473,0,95.01,42.538,95.01,95.01v950.312c0,52.473-42.538,95.01-95.01,95.01 H95.01c-52.473,0-95.01-42.538-95.01-95.01V561.51C0,509.038,42.538,466.5,95.01,466.5z"/>
|
||||
<path fill="#FFF" d="M820.211,828.193H630.241v517.297H509.211V828.193H320.123V727.844h500.088V828.193z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 627 KiB |
|
After Width: | Height: | Size: 188 KiB |
|
After Width: | Height: | Size: 864 KiB |
|
After Width: | Height: | Size: 401 KiB |
@@ -0,0 +1,564 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>HiveGrid Post-Generator</title>
|
||||
<link rel="stylesheet" href="./assets/fonts/manrope.css">
|
||||
<style>
|
||||
/* ─── HiveGrid design tokens (mirrored from app/globals.css) ─────────────
|
||||
Deliberately plain hex, no Tailwind/oklch: the export libraries choke on
|
||||
modern colour functions, so the slide subtree stays on values they can
|
||||
serialise. */
|
||||
:root {
|
||||
--accent-yellow: #F1B729;
|
||||
--accent-teal: #0D6E6E;
|
||||
--surface: #FFFFFF;
|
||||
--bg-primary: #FAFAFA;
|
||||
--bg-dark: #0F1A2E;
|
||||
--text-primary: #1A1A1A;
|
||||
--text-secondary:#555555;
|
||||
--text-on-dark: #C8D0DC;
|
||||
--text-white: #FFFFFF;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
body {
|
||||
font-family: 'Manrope', system-ui, sans-serif;
|
||||
background: #17181F; color: #E8EAEE;
|
||||
min-height: 100vh; display: flex; gap: 40px; padding: 32px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
/* ─── Projekt-Navigation ─────────────────────────────────────────────── */
|
||||
.projects { display: flex; gap: 6px; margin-bottom: 18px; }
|
||||
.projects a {
|
||||
flex: 1; text-align: center; text-decoration: none;
|
||||
font-size: 12px; font-weight: 700; padding: 9px 8px; border-radius: 8px;
|
||||
background: #22242C; color: #8A8F9B; border: 1px solid #2E313B;
|
||||
}
|
||||
.projects a:hover { background: #2A2D36; color: #C7CCD6; }
|
||||
.projects a.here { background: var(--accent-yellow); color: #1A1A1A; border-color: var(--accent-yellow); }
|
||||
.saved {
|
||||
font-size: 11px; color: #6FCF97; opacity: 0; transition: opacity .2s;
|
||||
margin-left: 6px;
|
||||
}
|
||||
.saved.on { opacity: 1; }
|
||||
.forget { font-size: 11px; color: #6E7480; background: none; border: 0;
|
||||
cursor: pointer; padding: 0; font-family: inherit; }
|
||||
.forget:hover { color: #EB7C6B; }
|
||||
|
||||
/* ─── Control panel ──────────────────────────────────────────────────── */
|
||||
.panel { width: 300px; flex: none; position: sticky; top: 32px; }
|
||||
.panel h1 { font-size: 19px; font-weight: 800; letter-spacing: -0.01em; margin-bottom: 4px; }
|
||||
.panel .sub { font-size: 13px; color: #8A8F9B; margin-bottom: 24px; line-height: 1.5; }
|
||||
|
||||
.group { margin-bottom: 22px; }
|
||||
.group > label {
|
||||
display: block; font-size: 11px; font-weight: 700; letter-spacing: 0.09em;
|
||||
text-transform: uppercase; color: #6E7480; margin-bottom: 9px;
|
||||
}
|
||||
|
||||
.opts { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
|
||||
.opts button {
|
||||
font-family: inherit; font-size: 13px; font-weight: 600;
|
||||
padding: 11px 10px; border-radius: 8px; cursor: pointer;
|
||||
background: #22242C; color: #C7CCD6; border: 1px solid #2E313B;
|
||||
transition: background .15s, border-color .15s, color .15s;
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
}
|
||||
.opts button:hover { background: #2A2D36; }
|
||||
.opts button[aria-pressed="true"] {
|
||||
background: var(--accent-yellow); color: #1A1A1A; border-color: var(--accent-yellow);
|
||||
}
|
||||
.swatch { width: 12px; height: 12px; border-radius: 3px; flex: none; border: 1px solid rgba(0,0,0,.18); }
|
||||
|
||||
.row { display: flex; gap: 8px; }
|
||||
.row button, .btn {
|
||||
font-family: inherit; font-size: 13.5px; font-weight: 700;
|
||||
padding: 13px 14px; border-radius: 8px; cursor: pointer; border: none;
|
||||
background: var(--accent-yellow); color: #1A1A1A; flex: 1; transition: filter .15s;
|
||||
}
|
||||
.row button:hover, .btn:hover { filter: brightness(1.07); }
|
||||
.btn.secondary { background: #22242C; color: #C7CCD6; border: 1px solid #2E313B; }
|
||||
.btn:disabled { opacity: .45; cursor: not-allowed; filter: none; }
|
||||
|
||||
.keys { font-size: 12px; color: #6E7480; line-height: 1.9; }
|
||||
.keys kbd {
|
||||
background: #22242C; border: 1px solid #343843; border-bottom-width: 2px;
|
||||
border-radius: 4px; padding: 1px 6px; font-family: ui-monospace, monospace;
|
||||
font-size: 11px; color: #C7CCD6;
|
||||
}
|
||||
.status { margin-top: 14px; font-size: 12.5px; color: #6E7480; min-height: 18px; }
|
||||
.status.ok { color: #6FCF97; }
|
||||
.status.err { color: #EB7C6B; }
|
||||
|
||||
/* ─── Stage ──────────────────────────────────────────────────────────── */
|
||||
.stage { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 14px; }
|
||||
.stage-meta { font-size: 12px; color: #6E7480; letter-spacing: .04em; }
|
||||
|
||||
/* The wrapper reserves the *scaled* footprint so the page doesn't jump;
|
||||
the slide itself always stays at true 1080×1350 so the export is
|
||||
pixel-exact with no resampling. */
|
||||
.slide-wrap { width: 486px; height: 607.5px; flex: none; }
|
||||
.slide-scaler { transform: scale(0.45); transform-origin: top left; }
|
||||
|
||||
/* ─── The slide — exactly Instagram 4:5 (1080 × 1350) ────────────────── */
|
||||
.slide {
|
||||
width: 1080px; height: 1350px;
|
||||
position: relative; overflow: hidden;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-family: 'Manrope', system-ui, sans-serif;
|
||||
}
|
||||
|
||||
/* Honeycomb — a real seamless tile across the full 1080×1350, generated in
|
||||
JS from the same G polygon as the mark. Full-bleed on purpose: cropping it
|
||||
inside a card made it read as a stray texture instead of a ground. */
|
||||
.pattern { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
|
||||
.slide[data-pattern="aus"] .pattern { display: none; }
|
||||
|
||||
/* Chamfered frame as an outline, not a filled plate — a filled card would
|
||||
cover the pattern, which is exactly what made the last version look cut off. */
|
||||
.frame { position: absolute; inset: 40px; pointer-events: none; }
|
||||
.slide[data-frame="aus"] .frame { display: none; }
|
||||
|
||||
.content {
|
||||
position: relative; z-index: 1;
|
||||
display: flex; flex-direction: column; align-items: center;
|
||||
text-align: center; padding: 0 104px;
|
||||
}
|
||||
|
||||
/* Eyebrow as a proper label: uppercase, tracked out, single line. Sentence
|
||||
case at this size wrapped onto two lines and read like body copy. */
|
||||
.eyebrow {
|
||||
font-size: 30px; font-weight: 700;
|
||||
letter-spacing: 0.15em; text-transform: uppercase;
|
||||
line-height: 1.2; white-space: nowrap;
|
||||
margin-bottom: 84px;
|
||||
}
|
||||
/* Top accent = colour (the marker is reserved for the payoff below). */
|
||||
.eyebrow .a { color: var(--accent-on); }
|
||||
|
||||
.mark { height: 320px; width: auto; display: block; }
|
||||
|
||||
.rule { width: 104px; height: 6px; border-radius: 3px; margin: 74px 0 34px; }
|
||||
|
||||
.tagline {
|
||||
font-size: 88px; font-weight: 800;
|
||||
letter-spacing: -0.028em; line-height: 1.04;
|
||||
}
|
||||
/* Bottom accent = marker. */
|
||||
.tagline .a {
|
||||
background: linear-gradient(transparent 58%, var(--marker) 58%, var(--marker) 94%, transparent 94%);
|
||||
-webkit-box-decoration-break: clone;
|
||||
box-decoration-break: clone;
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
/* ─── Variant: hell ──────────────────────────────────────────────────── */
|
||||
.slide[data-variant="hell"] {
|
||||
background: var(--surface);
|
||||
--accent-on: #C8901A; --marker: var(--accent-yellow);
|
||||
--pattern-color: var(--accent-yellow); --pattern-opacity: .085;
|
||||
--frame-color: #E5E5E5;
|
||||
}
|
||||
.slide[data-variant="hell"] .eyebrow { color: var(--text-secondary); }
|
||||
.slide[data-variant="hell"] .tagline { color: var(--text-primary); }
|
||||
.slide[data-variant="hell"] .mark { color: var(--accent-yellow); }
|
||||
.slide[data-variant="hell"] .rule { background: var(--accent-yellow); }
|
||||
|
||||
/* ─── Variant: dunkel ────────────────────────────────────────────────── */
|
||||
.slide[data-variant="dunkel"] {
|
||||
background: var(--bg-dark);
|
||||
--accent-on: var(--accent-yellow); --marker: var(--accent-yellow);
|
||||
--pattern-color: var(--accent-yellow); --pattern-opacity: .10;
|
||||
--frame-color: rgba(255,255,255,.16);
|
||||
}
|
||||
.slide[data-variant="dunkel"] .eyebrow { color: var(--text-on-dark); }
|
||||
.slide[data-variant="dunkel"] .tagline { color: var(--text-white); }
|
||||
.slide[data-variant="dunkel"] .mark { color: var(--accent-yellow); }
|
||||
.slide[data-variant="dunkel"] .rule { background: var(--accent-yellow); }
|
||||
.slide[data-variant="dunkel"] .tagline .a { color: #1A1A1A; }
|
||||
|
||||
/* ─── Variant: gelb ──────────────────────────────────────────────────── */
|
||||
.slide[data-variant="gelb"] {
|
||||
background: var(--accent-yellow);
|
||||
--accent-on: #FFFFFF; --marker: #1A1A1A;
|
||||
--pattern-color: #1A1A1A; --pattern-opacity: .07;
|
||||
--frame-color: rgba(26,26,26,.22);
|
||||
}
|
||||
.slide[data-variant="gelb"] .eyebrow { color: #6B5210; }
|
||||
.slide[data-variant="gelb"] .tagline { color: #1A1A1A; }
|
||||
.slide[data-variant="gelb"] .mark { color: #1A1A1A; }
|
||||
.slide[data-variant="gelb"] .rule { background: #1A1A1A; }
|
||||
.slide[data-variant="gelb"] .tagline .a { color: var(--accent-yellow); }
|
||||
|
||||
/* ─── Variant: teal ──────────────────────────────────────────────────── */
|
||||
.slide[data-variant="teal"] {
|
||||
background: var(--accent-teal);
|
||||
--accent-on: var(--accent-yellow); --marker: var(--accent-yellow);
|
||||
--pattern-color: #FFFFFF; --pattern-opacity: .07;
|
||||
--frame-color: rgba(255,255,255,.18);
|
||||
}
|
||||
.slide[data-variant="teal"] .eyebrow { color: #A8D4D4; }
|
||||
.slide[data-variant="teal"] .tagline { color: var(--text-white); }
|
||||
.slide[data-variant="teal"] .mark { color: var(--accent-yellow); }
|
||||
.slide[data-variant="teal"] .rule { background: var(--accent-yellow); }
|
||||
.slide[data-variant="teal"] .tagline .a { color: var(--accent-teal); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<aside class="panel">
|
||||
<h1>Post-Generator</h1>
|
||||
<div class="projects"><a class="" href="./post1.html">Post 1 · Lösung</a><a class="here" href="./index.html">Post 2 · Statement</a></div>
|
||||
<p class="sub">Einzelbild · 1080 × 1350 px<span class="saved" id="saved"></span><br><button class="forget" id="forget">Projekt zurücksetzen</button></p>
|
||||
|
||||
<div class="group">
|
||||
<label>Variante</label>
|
||||
<div class="opts" id="variants">
|
||||
<button data-v="hell" aria-pressed="true"><span class="swatch" style="background:#FFFFFF"></span>Hell</button>
|
||||
<button data-v="dunkel" aria-pressed="false"><span class="swatch" style="background:#0F1A2E"></span>Dunkel</button>
|
||||
<button data-v="gelb" aria-pressed="false"><span class="swatch" style="background:#F1B729"></span>Gelb</button>
|
||||
<button data-v="teal" aria-pressed="false"><span class="swatch" style="background:#0D6E6E"></span>Teal</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="group">
|
||||
<label>Wabenraster</label>
|
||||
<div class="opts" id="patterns">
|
||||
<button data-p="an" aria-pressed="true">An</button>
|
||||
<button data-p="aus" aria-pressed="false">Aus</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="group">
|
||||
<label>Dichte</label>
|
||||
<div class="opts" id="densities">
|
||||
<button data-d="0.40" aria-pressed="false">Fein</button>
|
||||
<button data-d="0.55" aria-pressed="true">Mittel</button>
|
||||
<button data-d="0.75" aria-pressed="false">Grob</button>
|
||||
<button data-d="1.00" aria-pressed="false">XL</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="group">
|
||||
<label>Rahmen</label>
|
||||
<div class="opts" id="frames">
|
||||
<button data-f="an" aria-pressed="true">An</button>
|
||||
<button data-f="aus" aria-pressed="false">Aus</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="group">
|
||||
<label>Download</label>
|
||||
<div class="row" style="margin-bottom:8px">
|
||||
<button id="dl-png" disabled>PNG</button>
|
||||
<button id="dl-jpg" class="btn secondary" disabled>JPG</button>
|
||||
</div>
|
||||
<button id="dl-all" class="btn secondary" style="width:100%" disabled>Alle 4 Varianten als ZIP</button>
|
||||
</div>
|
||||
|
||||
<div class="group" style="margin-bottom:0">
|
||||
<label>Tastatur</label>
|
||||
<div class="keys">
|
||||
<kbd>1</kbd>–<kbd>4</kbd> Variante · <kbd>P</kbd> Raster · <kbd>R</kbd> Rahmen<br>
|
||||
<kbd>D</kbd> PNG laden · <kbd>A</kbd> alle als ZIP
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="status" id="status">Lade Schriften und Assets ...</div>
|
||||
</aside>
|
||||
|
||||
<main class="stage">
|
||||
<div class="slide-wrap">
|
||||
<div class="slide-scaler">
|
||||
|
||||
<!-- ─── SLIDE ─────────────────────────────────────────────────────── -->
|
||||
<div class="slide" id="slide" data-variant="hell" data-pattern="an" data-frame="an">
|
||||
|
||||
<svg class="pattern" id="pattern" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"></svg>
|
||||
|
||||
<svg class="frame" viewBox="0 0 1000 1270" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<polygon points="34,0 966,0 1000,34 1000,1236 966,1270 34,1270 0,1236 0,34"
|
||||
stroke="var(--frame-color)" stroke-width="3"/>
|
||||
</svg>
|
||||
|
||||
<div class="content">
|
||||
<p class="eyebrow">Vom <span class="a">Handwerker</span> für den <span class="a">Handwerker</span></p>
|
||||
|
||||
<!-- G mark only, lifted straight out of logo.svg (the single yellow
|
||||
polygon), normalised to a 0,0-based 384.5 × 332.9 box. Inline
|
||||
rather than <img> so each variant recolours it via currentColor. -->
|
||||
<svg class="mark" viewBox="0 0 384.5 332.9" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="HiveGrid">
|
||||
<polygon fill="currentColor" points="384.5,166.5 336.4,83.2 192.2,166.5 288.3,166.5 240.3,249.7 144.2,249.7 96.1,166.5 144.2,83.2 336.4,83.2 298,16.8 288.3,0 96.1,0 0,166.5 96.1,332.9 288.3,332.9"/>
|
||||
</svg>
|
||||
|
||||
<div class="rule"></div>
|
||||
|
||||
<p class="tagline"><span class="a">Verbinden</span><br>statt ersetzen</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ─── /SLIDE ────────────────────────────────────────────────────── -->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<p class="stage-meta">Vorschau 45 %, Export in voller Auflösung</p>
|
||||
</main>
|
||||
|
||||
<script src="./vendor/html-to-image.js"></script>
|
||||
<script src="./vendor/jszip.min.js"></script>
|
||||
<script>
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
/* A syntax or temporal-dead-zone error at this level kills the whole script
|
||||
silently: the panel renders from static markup, but nothing is ever built
|
||||
and no status is set. Surface it instead of showing an empty stage. */
|
||||
window.addEventListener("error", e => {
|
||||
const el = document.getElementById("status");
|
||||
if (el) {
|
||||
el.textContent = "Skriptfehler: " + (e.message || e.error);
|
||||
el.className = "status err";
|
||||
}
|
||||
});
|
||||
|
||||
const slide = document.getElementById("slide");
|
||||
const statusEl = document.getElementById("status");
|
||||
const patternEl = document.getElementById("pattern");
|
||||
|
||||
const VARIANTS = ["hell", "dunkel", "gelb", "teal"];
|
||||
const SVG_NS = "http://www.w3.org/2000/svg";
|
||||
|
||||
// The G mark, normalised to a 0,0-based box.
|
||||
const G_POINTS = "384.5,166.5 336.4,83.2 192.2,166.5 288.3,166.5 240.3,249.7 144.2,249.7 96.1,166.5 144.2,83.2 336.4,83.2 298,16.8 288.3,0 96.1,0 0,166.5 96.1,332.9 288.3,332.9";
|
||||
const G_W = 384.5, G_H = 332.9;
|
||||
|
||||
let fontEmbedCss = "";
|
||||
|
||||
/* ─── Projektstand ────────────────────────────────────────────────────────
|
||||
Gleiches Prinzip wie bei Post 1: Das Werkzeug hält den Stand. */
|
||||
const STORE = "hivegrid.projekt.post2";
|
||||
let savedTimer = null;
|
||||
|
||||
function markSaved() {
|
||||
const el = document.getElementById("saved");
|
||||
if (!el) return;
|
||||
el.textContent = "gespeichert";
|
||||
el.classList.add("on");
|
||||
clearTimeout(savedTimer);
|
||||
savedTimer = setTimeout(() => el.classList.remove("on"), 1200);
|
||||
}
|
||||
|
||||
function saveState() {
|
||||
try {
|
||||
localStorage.setItem(STORE, JSON.stringify({
|
||||
v: 1,
|
||||
variant: slide.dataset.variant,
|
||||
pattern: slide.dataset.pattern,
|
||||
frame: slide.dataset.frame,
|
||||
density: currentDensity,
|
||||
}));
|
||||
markSaved();
|
||||
} catch (err) { console.warn("Speichern fehlgeschlagen", err); }
|
||||
}
|
||||
|
||||
function loadState() {
|
||||
let d = null;
|
||||
try { d = JSON.parse(localStorage.getItem(STORE) || "null"); } catch (_) {}
|
||||
if (!d || d.v !== 1) return false;
|
||||
if (d.variant) setVariant(d.variant);
|
||||
if (d.pattern) setPattern(d.pattern);
|
||||
if (d.frame) setFrame(d.frame);
|
||||
if (d.density) setDensity(d.density);
|
||||
return true;
|
||||
}
|
||||
|
||||
function forgetProject() {
|
||||
try { localStorage.removeItem(STORE); } catch (_) {}
|
||||
location.reload();
|
||||
}
|
||||
|
||||
function setStatus(msg, kind) {
|
||||
statusEl.textContent = msg;
|
||||
statusEl.className = "status" + (kind ? " " + kind : "");
|
||||
}
|
||||
|
||||
/* Builds a seamless flat-top honeycomb tile.
|
||||
For flat-top hexagons the columns step by 3/4 of the width and neighbouring
|
||||
columns are offset vertically by half a row, so one tile = two columns wide
|
||||
and one row high. The copies hanging outside the tile are what makes the
|
||||
repeat seamless — without them you get the visible seams of the last try. */
|
||||
function buildPattern(scale) {
|
||||
const colStep = G_W * 0.75; // 288.4
|
||||
const rowStep = G_H; // 332.9
|
||||
const tileW = colStep * 2; // 576.8
|
||||
const tileH = rowStep;
|
||||
|
||||
// Hex centres that touch this tile, including the ones bleeding over the edges.
|
||||
const centres = [
|
||||
[0, 0], [tileW, 0], [0, tileH], [tileW, tileH],
|
||||
[colStep, rowStep / 2], [colStep, -rowStep / 2], [colStep, tileH + rowStep / 2],
|
||||
];
|
||||
|
||||
while (patternEl.firstChild) patternEl.removeChild(patternEl.firstChild);
|
||||
|
||||
const defs = document.createElementNS(SVG_NS, "defs");
|
||||
const pat = document.createElementNS(SVG_NS, "pattern");
|
||||
pat.setAttribute("id", "hexpat");
|
||||
pat.setAttribute("patternUnits", "userSpaceOnUse");
|
||||
pat.setAttribute("width", tileW);
|
||||
pat.setAttribute("height", tileH);
|
||||
pat.setAttribute("patternTransform", "scale(" + scale + ")");
|
||||
|
||||
for (const [cx, cy] of centres) {
|
||||
const p = document.createElementNS(SVG_NS, "polygon");
|
||||
p.setAttribute("points", G_POINTS);
|
||||
p.setAttribute("fill", "currentColor");
|
||||
p.setAttribute("transform", "translate(" + (cx - G_W / 2) + "," + (cy - G_H / 2) + ")");
|
||||
pat.appendChild(p);
|
||||
}
|
||||
|
||||
defs.appendChild(pat);
|
||||
patternEl.appendChild(defs);
|
||||
|
||||
const rect = document.createElementNS(SVG_NS, "rect");
|
||||
rect.setAttribute("width", "100%");
|
||||
rect.setAttribute("height", "100%");
|
||||
rect.setAttribute("fill", "url(#hexpat)");
|
||||
patternEl.appendChild(rect);
|
||||
|
||||
// Colour + opacity come from the active variant.
|
||||
patternEl.style.color = "var(--pattern-color)";
|
||||
patternEl.style.opacity = "var(--pattern-opacity)";
|
||||
}
|
||||
|
||||
function press(containerId, attr, value) {
|
||||
document.querySelectorAll("#" + containerId + " button").forEach(b =>
|
||||
b.setAttribute("aria-pressed", String(b.dataset[attr] === value))
|
||||
);
|
||||
}
|
||||
|
||||
function setVariant(v) { slide.dataset.variant = v; press("variants", "v", v); }
|
||||
function setPattern(p) { slide.dataset.pattern = p; press("patterns", "p", p); }
|
||||
function setFrame(f) { slide.dataset.frame = f; press("frames", "f", f); }
|
||||
let currentDensity = "0.55";
|
||||
function setDensity(d) { currentDensity = d; buildPattern(Number(d)); press("densities", "d", d); }
|
||||
|
||||
/* Renders at pixelRatio 1 against a node that is already 1080×1350, so the
|
||||
output is exactly Instagram's spec with no upscaling artefacts. */
|
||||
function renderOptions() {
|
||||
return {
|
||||
width: 1080, height: 1350, pixelRatio: 1,
|
||||
fontEmbedCSS: fontEmbedCss,
|
||||
// The scaler transform is a preview-only concern — neutralise it for the
|
||||
// export, otherwise the bitmap comes out at 45 %.
|
||||
style: { transform: "none", transformOrigin: "top left" },
|
||||
};
|
||||
}
|
||||
|
||||
/* A 1080x1350 PNG as a data: URL runs to several megabytes. Browsers routinely
|
||||
ignore the download attribute on those and navigate to the URL instead,
|
||||
which shows up as a blank tab. Blob URLs are handled properly. */
|
||||
/* Local-only save endpoint, see serve.py. */
|
||||
async function downloadBlob(blob, filename) {
|
||||
const res = await fetch("/save", {
|
||||
method: "POST",
|
||||
headers: { "X-Filename": filename },
|
||||
body: blob,
|
||||
});
|
||||
if (!res.ok) throw new Error("Speichern fehlgeschlagen: HTTP " + res.status);
|
||||
return (await res.json()).path;
|
||||
}
|
||||
|
||||
const nameFor = ext => "hivegrid-post2-statement-" + slide.dataset.variant + "." + ext;
|
||||
|
||||
async function downloadPng() {
|
||||
setStatus("Rendere PNG ...");
|
||||
const blob = await htmlToImage.toBlob(slide, renderOptions());
|
||||
if (!blob) throw new Error("Rendern lieferte kein Bild");
|
||||
downloadBlob(blob, nameFor("png"));
|
||||
setStatus("PNG gespeichert. 1080 × 1350", "ok");
|
||||
}
|
||||
|
||||
async function downloadJpg() {
|
||||
setStatus("Rendere JPG ...");
|
||||
const opts = Object.assign(renderOptions(), { quality: 0.9 });
|
||||
const url = await htmlToImage.toJpeg(slide, opts);
|
||||
const blob = await (await fetch(url)).blob();
|
||||
downloadBlob(blob, nameFor("jpg"));
|
||||
setStatus("JPG gespeichert. Qualität 90 %", "ok");
|
||||
}
|
||||
|
||||
async function downloadAll() {
|
||||
const zip = new JSZip();
|
||||
const keep = slide.dataset.variant;
|
||||
for (const v of VARIANTS) {
|
||||
setStatus("Rendere " + v + " ...");
|
||||
setVariant(v);
|
||||
await document.fonts.ready;
|
||||
zip.file("hivegrid-post2-statement-" + v + ".png",
|
||||
await htmlToImage.toBlob(slide, renderOptions()));
|
||||
}
|
||||
setVariant(keep);
|
||||
setStatus("Packe ZIP ...");
|
||||
downloadBlob(await zip.generateAsync({ type: "blob" }), "hivegrid-post2-statement.zip");
|
||||
setStatus("4 Varianten als ZIP gespeichert", "ok");
|
||||
}
|
||||
|
||||
function guard(fn) {
|
||||
return () => fn().catch(err => {
|
||||
console.error(err);
|
||||
setStatus("Fehler: " + err.message, "err");
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById("variants").addEventListener("click", e => {
|
||||
const b = e.target.closest("button"); if (b) { setVariant(b.dataset.v); saveState(); }
|
||||
});
|
||||
document.getElementById("patterns").addEventListener("click", e => {
|
||||
const b = e.target.closest("button"); if (b) { setPattern(b.dataset.p); saveState(); }
|
||||
});
|
||||
document.getElementById("frames").addEventListener("click", e => {
|
||||
const b = e.target.closest("button"); if (b) { setFrame(b.dataset.f); saveState(); }
|
||||
});
|
||||
document.getElementById("densities").addEventListener("click", e => {
|
||||
const b = e.target.closest("button"); if (b) { setDensity(b.dataset.d); saveState(); }
|
||||
});
|
||||
|
||||
document.getElementById("dl-png").addEventListener("click", guard(downloadPng));
|
||||
document.getElementById("dl-jpg").addEventListener("click", guard(downloadJpg));
|
||||
document.getElementById("dl-all").addEventListener("click", guard(downloadAll));
|
||||
|
||||
document.addEventListener("keydown", e => {
|
||||
if (e.metaKey || e.ctrlKey || e.altKey) return;
|
||||
const k = e.key.toLowerCase();
|
||||
if (k >= "1" && k <= "4") setVariant(VARIANTS[Number(k) - 1]);
|
||||
else if (k === "p") setPattern(slide.dataset.pattern === "an" ? "aus" : "an");
|
||||
else if (k === "r") setFrame(slide.dataset.frame === "an" ? "aus" : "an");
|
||||
else if (k === "d") guard(downloadPng)();
|
||||
else if (k === "a") guard(downloadAll)();
|
||||
});
|
||||
|
||||
/* Boot: build the tile, wait for Manrope to actually be resident (otherwise
|
||||
the first export silently falls back to a system font), and pre-compute the
|
||||
font CSS once instead of on every render. */
|
||||
(async function boot() {
|
||||
try {
|
||||
buildPattern(0.55);
|
||||
const restored = loadState();
|
||||
document.getElementById("forget").addEventListener("click", forgetProject);
|
||||
await document.fonts.ready;
|
||||
fontEmbedCss = await htmlToImage.getFontEmbedCSS(slide);
|
||||
["dl-png", "dl-jpg", "dl-all"].forEach(id => document.getElementById(id).disabled = false);
|
||||
setStatus(restored ? "Projektstand geladen" : "Bereit. 1080 × 1350 px", "ok");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setStatus("Fehler beim Laden: " + err.message, "err");
|
||||
}
|
||||
})();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,561 @@
|
||||
# Post-Generator — Regeln & Konventionen
|
||||
|
||||
Arbeitsgrundlage für alles, was in diesem Repo an Social-Grafiken entsteht.
|
||||
**Stand:** 31.07.2026
|
||||
|
||||
Der Generator läuft lokal: `./serve.sh` → <http://127.0.0.1:4321/post1.html>
|
||||
(Post 1, Carousel) bzw. `/index.html` (Post 2, Statement).
|
||||
|
||||
---
|
||||
|
||||
## 1. Wo die Wahrheit steht
|
||||
|
||||
Alle Marken-Werte stammen aus der Website, nicht aus diesem Repo. Bei Zweifel
|
||||
**dort nachschauen, nicht hier raten**:
|
||||
|
||||
| Was | Quelle |
|
||||
|---|---|
|
||||
| Farben, Radien, Abstände | `HiveGrid-Website/app/globals.css` (`:root`) |
|
||||
| Chamfer-Form | `HiveGrid-Website/lib/chamfer.ts` |
|
||||
| Schrift | `HiveGrid-Website/app/layout.tsx` |
|
||||
| Badge-Styling | `HiveGrid-Website/components/sections/ScreenshotAccordion.tsx` |
|
||||
| Feature-Texte | `HiveGrid-Website/app/playground/PlaygroundClient.tsx` → `solutionModules` |
|
||||
|
||||
Die Werte sind hier gespiegelt, weil der Export keine Tailwind-/oklch-Farben
|
||||
verträgt (siehe §7). Ändert sich die Website, müssen sie **hier nachgezogen
|
||||
werden** — das passiert nicht automatisch.
|
||||
|
||||
---
|
||||
|
||||
## 2. Farben
|
||||
|
||||
### Marke
|
||||
|
||||
| Token | Wert | Verwendung |
|
||||
|---|---|---|
|
||||
| `--accent-yellow` | `#F1B729` | Primärer Akzent, Marker, G-Marke |
|
||||
| `--accent-yellow-light` | `#FFE082` | Badge-Hintergrund |
|
||||
| `--accent-yellow-dark` | `#3E2C0A` | Badge-Text |
|
||||
| `--accent-teal` | `#0D6E6E` | Sekundär, aktuell ungenutzt |
|
||||
| `--dark-bg` | `#30313D` | Dunkle Variante + Formen-Ebene |
|
||||
|
||||
### Flächen & Text
|
||||
|
||||
| Token | Wert |
|
||||
|---|---|
|
||||
| `--surface` | `#FFFFFF` |
|
||||
| `--bg-primary` | `#FAFAFA` |
|
||||
| `--text-primary` | `#1A1A1A` |
|
||||
| `--text-secondary` | `#555555` |
|
||||
| `--text-on-dark` | `#C8D0DC` |
|
||||
|
||||
### Pro Variante
|
||||
|
||||
**Hell** (Grund `#FFFFFF`)
|
||||
|
||||
```
|
||||
--head-color #1A1A1A --frame-bg #E8EBF0
|
||||
--body-color #555555 --shape-fill #30313D (deckend!)
|
||||
--accent-on #C8901A --shape-line rgba(48,49,61,.13)
|
||||
--marker #F1B729 --shape-accent #F1B729
|
||||
--pat-color #30313D --gmark-color rgba(48,49,61,.16)
|
||||
```
|
||||
|
||||
**Dunkel** (Grund `#30313D`)
|
||||
|
||||
```
|
||||
--head-color #FFFFFF --frame-bg rgba(255,255,255,.11)
|
||||
--body-color #B9BDC9 --shape-fill #414252
|
||||
--accent-on #F1B729 --shape-line rgba(255,255,255,.10)
|
||||
--marker #F1B729 --shape-accent #F1B729
|
||||
--pat-color #FFFFFF --gmark-color rgba(255,255,255,.13)
|
||||
```
|
||||
|
||||
> **`--accent-on` ist nicht `#F1B729`, sondern `#C8901A` auf Hell.** Reines
|
||||
> Markengelb trägt auf Weiß bei kleinem Text nicht. Der abgedunkelte Ton ist
|
||||
> Absicht, kein Fehler.
|
||||
|
||||
> **Der Block oben rechts ist auf Hell deckendes `#30313D`.** Das ist derselbe
|
||||
> Wert wie die dunkle Story-Sektion der Website. Er darf **nicht** über die
|
||||
> Intensitätsstufe aufgehellt werden, sonst ist es nicht mehr die Markenfarbe
|
||||
> (siehe §5).
|
||||
|
||||
---
|
||||
|
||||
## 3. Geometrie
|
||||
|
||||
### Chamfer
|
||||
|
||||
Die Marken-Form: abgeschnittene Ecken, Echo der Honigwabe. Auf der Website
|
||||
16 px, hier hochskaliert, weil die Fläche viermal so groß ist.
|
||||
|
||||
```css
|
||||
clip-path: polygon(
|
||||
Xpx 0, calc(100% - Xpx) 0, 100% Xpx, 100% calc(100% - Xpx),
|
||||
calc(100% - Xpx) 100%, Xpx 100%, 0 calc(100% - Xpx), 0 Xpx
|
||||
);
|
||||
```
|
||||
|
||||
| Element | X |
|
||||
|---|---|
|
||||
| Badge | 11 px |
|
||||
| Mockup-Rahmen außen | 22 px |
|
||||
| Mockup-Rahmen innen | 14 px |
|
||||
|
||||
**Drei Fallstricke** (stehen auch in `chamfer.ts`):
|
||||
|
||||
- `clip-path` beschneidet **auch den Schatten** → Tiefe über `filter: drop-shadow()`, nie `box-shadow`
|
||||
- `clip-path` beschneidet **auch den Rahmen** → Rand über eine Hintergrundfläche lösen, nicht über `border`
|
||||
- Kinder, die aus der Box ragen (Popups, Tooltips), werden abgeschnitten → solche Elemente nicht chamfern
|
||||
|
||||
### Hexagon
|
||||
|
||||
Flat-Top, also flache Ober- und Unterkante mit Spitzen links und rechts:
|
||||
|
||||
```css
|
||||
clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);
|
||||
```
|
||||
|
||||
**Seitenverhältnis 384,5 : 332,9 = 1,155 (= 2/√3).** Aus der Logomarke
|
||||
abgeleitet, nicht geschätzt. Wer davon abweicht, staucht das Sechseck.
|
||||
|
||||
### G-Marke
|
||||
|
||||
Ein einziges Polygon, aus `logo.svg` extrahiert (dort die einzige Fläche mit
|
||||
`class="st3"`), auf 0,0 normalisiert:
|
||||
|
||||
```
|
||||
384.5,166.5 336.4,83.2 192.2,166.5 288.3,166.5 240.3,249.7 144.2,249.7
|
||||
96.1,166.5 144.2,83.2 336.4,83.2 298,16.8 288.3,0 96.1,0 0,166.5
|
||||
96.1,332.9 288.3,332.9
|
||||
```
|
||||
|
||||
Immer **inline** als SVG einsetzen, nie als `<img>` — nur so lässt sie sich per
|
||||
`currentColor` pro Variante umfärben, und nur so überlebt sie den Export
|
||||
zuverlässig (§7).
|
||||
|
||||
### Wabenraster
|
||||
|
||||
Nahtlose Kachel aus derselben G-Form. Bei Flat-Top-Hexagonen springen die
|
||||
Spalten um ¾ der Breite, benachbarte Spalten sitzen eine halbe Reihe versetzt.
|
||||
Eine Kachel = zwei Spalten breit, eine Reihe hoch. **Die über den Kachelrand
|
||||
hinausragenden Kopien sind Pflicht** — ohne sie sieht man die Nähte.
|
||||
|
||||
---
|
||||
|
||||
## 4. Typografie
|
||||
|
||||
### Die Schrift
|
||||
|
||||
**Manrope** — dieselbe wie auf der Website. Bewusst nicht Inter: Der Kommentar in
|
||||
`app/layout.tsx` nennt Inter „the default AI/website-builder font"; Manrope ist
|
||||
eine Spur wärmer und geometrischer.
|
||||
|
||||
```css
|
||||
font-family: 'Manrope', system-ui, sans-serif;
|
||||
```
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Schnitte | 400, 500, 600, 700, 800 |
|
||||
| Ablage | `assets/fonts/manrope.css` + 6 `.woff2` |
|
||||
| Subsets | latin, latin-ext, cyrillic, cyrillic-ext, greek, vietnamese |
|
||||
| Herkunft | Google Fonts, einmalig heruntergeladen |
|
||||
|
||||
**Warum lokal und nicht per CDN:** Der Export darf zur Laufzeit an keiner
|
||||
fremden Origin hängen. Ein Font von `fonts.gstatic.com` wäre cross-origin, würde
|
||||
den Export vom Netz abhängig machen und im Zweifel den Canvas verunreinigen. Die
|
||||
URLs in `manrope.css` sind deshalb auf relative Pfade umgeschrieben.
|
||||
|
||||
> **Vor jedem Export auf `document.fonts.ready` warten.** Sonst rendert der erste
|
||||
> Durchgang still in einer System-Fallback-Schrift, und man sieht es erst in der
|
||||
> fertigen Datei. Der Download-Button bleibt bis dahin deaktiviert.
|
||||
|
||||
### Größen
|
||||
|
||||
| Element | Größe | Weight | Sonstiges |
|
||||
|---|---|---|---|
|
||||
| Headline | 74 px | 800 | `line-height: 1.06`, `letter-spacing: -0.03em` |
|
||||
| Titel-Tagline | 74 px | 800 | identisch zur Headline, bewusst |
|
||||
| Fließtext | 30 px | 500 | `line-height: 1.48`, `max-width: 830px` |
|
||||
| Badge | 26 px | 600 | `letter-spacing: 0.1em` |
|
||||
| Claim (Titel) | 31 px | 500 | `line-height: 1.44` |
|
||||
|
||||
### Akzent-Wörter
|
||||
|
||||
Zwei Behandlungen, **nie beide im selben Textblock**:
|
||||
|
||||
- **Marker** — handgezeichneter SVG-Strich als `background-image`, wellige
|
||||
Kanten, angeschrägtes linkes Ende. Für Headlines und Payoffs.
|
||||
Ein CSS-Verlauf ist hier **verboten**: er kann geometrisch nur ein perfektes
|
||||
Rechteck erzeugen und sieht gedruckt statt gemalt aus.
|
||||
- **Farbe** (`--accent-on`) — für kleinere Zeilen.
|
||||
|
||||
Pro Slide höchstens **ein** markiertes Wort. Zwei Marker in einem Block waren
|
||||
schon zweimal zu viel.
|
||||
|
||||
### Sprache
|
||||
|
||||
- Alles Sichtbare auf **Deutsch**, Code-Kommentare auf **Englisch**
|
||||
- **Keine Em-Dashes (—)** in sichtbaren Texten. Komma, Punkt oder `·`
|
||||
- Feature-Texte wortgleich von der Website. Headlines dürfen gekürzt und
|
||||
umgestellt werden, aber **keine neuen Wörter erfinden**
|
||||
|
||||
---
|
||||
|
||||
## 5. Aufbau der Slides
|
||||
|
||||
### Formate
|
||||
|
||||
**1080 × 1350 px (4:5)** — das hochkantigste offiziell unterstützte
|
||||
Feed-Format. Belegt: Instagram unterstützt 1.91:1 bis 4:5, Breiten 320–1080
|
||||
behalten die Originalauflösung, alles außerhalb wird beschnitten.
|
||||
|
||||
- **Alle Slides eines Carousels müssen dasselbe Seitenverhältnis haben** — die
|
||||
erste Slide setzt es, der Rest wird hart darauf beschnitten
|
||||
- Safe Zone: nichts Lesbares näher als ~92 px an den Rand
|
||||
- Der **Profil-Grid** zeigt seit 2025 Hochformat und beschneidet auf 3:4 —
|
||||
im Grid fehlen links und rechts je ~33 px. Für die drei angepinnten Cover
|
||||
relevant, im Feed nicht
|
||||
|
||||
### Layout-Template: Tag, Überschrift, Beschreibung, Bild
|
||||
|
||||
Jede Feature-Slide besteht aus denselben vier Bausteinen in derselben Reihenfolge.
|
||||
Das ist keine Geschmacksfrage — Wiedererkennung über fünf Slides entsteht genau
|
||||
dadurch, dass sich das Gerüst nicht bewegt.
|
||||
|
||||
```
|
||||
92 ┌──────────────────────────────────────┐
|
||||
│ ▌ TAG │ ← ausgerückt (siehe unten)
|
||||
│ │
|
||||
│ Überschrift, zwei Zeilen │ ← dominiert die Slide
|
||||
│ mit einem markierten Wort │
|
||||
│ │
|
||||
│ Beschreibung, zwei bis drei │ ← kürzer als man denkt
|
||||
│ Zeilen Fließtext │
|
||||
│ │
|
||||
│ ┌────────────────────────────────── │
|
||||
│ │ Bild │ ← ein dominanter Blickfang
|
||||
│ │ │
|
||||
└──┴───────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Größenverhältnisse.** Die Hierarchie muss ohne Nachdenken lesbar sein — was
|
||||
zuerst, was zweitens, was drittens. Bei uns: Überschrift 74 px, Fließtext 30 px,
|
||||
Tag 26 px. Das Verhältnis Überschrift zu Fließtext liegt bei rund **2,5 : 1**;
|
||||
gängige Empfehlungen für Carousels nennen Überschrift 48–72 pt gegen Fließtext
|
||||
24–36 pt, also dieselbe Größenordnung. Weniger Abstand als Faktor 2 und die
|
||||
Hierarchie kippt.
|
||||
|
||||
**Ein Blickfang pro Slide.** Genau ein Element, auf das das Auge sofort springt —
|
||||
bei uns die Überschrift, danach das Bild. Zwei gleich starke Elemente lassen den
|
||||
Betrachter raten und wegwischen.
|
||||
|
||||
**Weißraum ist kein verschenkter Platz**, sondern das, was die Blöcke trennt.
|
||||
Der Abstand zwischen Tag und Überschrift ist deshalb größer als der zwischen
|
||||
Überschrift und Beschreibung: getrennte Gedanken brauchen mehr Luft als
|
||||
zusammengehörige.
|
||||
|
||||
#### Das Tag wird ausgerückt
|
||||
|
||||
Der Tag hat 20 px Innenabstand. Steht sein **Kasten** bündig zur Inhaltskante,
|
||||
beginnt sein **Text** 20 px weiter rechts als die Überschrift — und genau das
|
||||
sieht man:
|
||||
|
||||
```
|
||||
vorher nachher
|
||||
92 │▌ WORKFLOW-ENGINE 72 │▌ WORKFLOW-ENGINE
|
||||
92 │Wenn-dann 92 │Wenn-dann
|
||||
└ Kasten bündig, └ Text bündig,
|
||||
Text 20 px versetzt Kasten ragt aus
|
||||
```
|
||||
|
||||
Gelöst über `margin-left: -20px`, also genau den Innenabstand. Das ist dasselbe
|
||||
Prinzip wie hängende Interpunktion im Satz: Ausgerichtet wird nach der
|
||||
**wahrgenommenen** Kante, nicht nach der geometrischen. Der Kasten steht dann bei
|
||||
x 72 — immer noch weit innerhalb der Safe Zone.
|
||||
|
||||
> Wer die Innenabstände des Tags ändert, muss das negative Margin mitziehen.
|
||||
> Die beiden Werte gehören zusammen.
|
||||
|
||||
### Ebenen (von hinten nach vorn)
|
||||
|
||||
| z-index | Ebene |
|
||||
|---|---|
|
||||
| 0 | Hintergrund: Formen, Wabenraster, Punkte, Raster, G-Marke |
|
||||
| 1 | Mockup |
|
||||
| 2 | Inhalt: Badge, Headline, Fließtext |
|
||||
| 3 | Swipe-Hinweis |
|
||||
|
||||
### Was global gilt und was pro Slide
|
||||
|
||||
| Einstellung | Geltung | Warum |
|
||||
|---|---|---|
|
||||
| Variante (Hell/Dunkel) | **global** | gemischt fällt das Carousel auseinander |
|
||||
| Mockup-Perspektive | **global** | dito |
|
||||
| Renderqualität | **global** | Exportstufe, kein Gestaltungsmittel (§7) |
|
||||
| Layout | pro Slide | jede Slide hat einen eigenen Katalog |
|
||||
| Muster | pro Slide | |
|
||||
| Intensität | pro Slide | |
|
||||
|
||||
### Durchgehendes Carousel (Panorama)
|
||||
|
||||
Der Post ist dann kein Stapel von fünf Bildern mehr, sondern **ein einziges
|
||||
Blatt von 5400 × 1350 px**, das in fünf 1080er Fenster geschnitten wird. Wer
|
||||
swipet, sieht Elemente über die Kante hinauslaufen und auf der nächsten Slide
|
||||
weitergehen.
|
||||
|
||||
```
|
||||
Slide 1 │ Slide 2 │ Slide 3 │ Slide 4 │ Slide 5
|
||||
0 ─────── 1080 ────── 2160 ────── 3240 ────── 4320 ────── 5400
|
||||
↑ ↑ ↑ ↑
|
||||
die vier Nähte
|
||||
```
|
||||
|
||||
**Technisch:** Eine Ebene von 5400 px Breite liegt in jeder Slide, verschoben um
|
||||
`index × -1080 px`. Jede Slide zeigt dadurch ihr Fenster desselben Blatts. Kein
|
||||
Zuschneiden von Hand, kein Splitter-Tool — die Slides *sind* die Schnitte.
|
||||
|
||||
**Regeln, die dabei zählen:**
|
||||
|
||||
1. **Slide 1 muss allein funktionieren.** Sie ist das Cover im Profil-Grid. Sie
|
||||
darf nicht wie „das linke Fünftel von etwas" wirken
|
||||
2. **Die Naht ist nie unsichtbar.** Instagram zeigt beim Swipen ein paar Pixel
|
||||
Abstand zwischen den Slides. Das lässt sich nicht abstellen und ist auch nicht
|
||||
schlimm — der Effekt trägt trotzdem
|
||||
3. **Nichts Wichtiges auf die Naht legen.** Keine Schrift, kein Gesicht, kein
|
||||
Logo. Der Schnitt gehört in ruhige Zonen
|
||||
4. **Aber etwas Auffälliges über die Naht laufen lassen.** Eine halbierte Form
|
||||
zieht das Auge weiter und lenkt vom Spalt ab. Genau dafür sitzen unsere
|
||||
Sechsecke **mittig auf 1080 / 2160 / 3240 / 4320** — je zur Hälfte links und
|
||||
rechts der Naht
|
||||
5. **Nur Standbilder.** Ein Video mitten im Carousel bricht den Fluss
|
||||
|
||||
**Was bei uns auf dem Blatt liegt:** vier Sechsecke auf den vier Nähten
|
||||
(abwechselnd massiv und Kontur, unterschiedliche Höhen), die G-Marke auf der Naht
|
||||
2|3, und ein durchgehendes Band über die volle Breite als Klammer.
|
||||
|
||||
Das ist ein **globaler** Schalter, kein Muster pro Slide — er wirkt naturgemäß
|
||||
auf alle fünf. Die Muster pro Slide bleiben zusätzlich verfügbar; für den reinen
|
||||
Panorama-Look überall auf „Aus" stellen.
|
||||
|
||||
> Gängige Empfehlung sind 2–4 Slides fürs Panorama. Wir haben fünf. Das
|
||||
> funktioniert, verlangt aber eine ruhigere Gestaltung — bei fünf Fenstern wird
|
||||
> jede zusätzliche Form schnell unruhig.
|
||||
|
||||
### Intensität
|
||||
|
||||
Die Stufe „Dezent" (Faktor 0,55) wirkt **nur auf Nebenelemente** — Hexagon-Kontur,
|
||||
Waben, Punkt- und Linienraster, G-Marke. Der große Block oben rechts bleibt
|
||||
immer deckend, sonst wäre er nicht mehr exakt `#30313D`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Mockups
|
||||
|
||||
Kein fotorealistischer Laptop-Rahmen für die Feature-Slides: Die Website rahmt
|
||||
jeden Screenshot mit dem Chamfer (`.screenshot-frame` in `globals.css`). Das ist
|
||||
die markeneigene Mockup-Form und verbindet die Posts mit der Seite.
|
||||
|
||||
Der Laptop auf der Titel-Slide ist die Ausnahme und bewusst ein Blickfang.
|
||||
|
||||
### Geometrie pro Slide
|
||||
|
||||
Breite, Seitenverhältnis und Position sind CSS-Variablen mit Defaults:
|
||||
|
||||
```
|
||||
--mock-w 1090px (ragt ~100 px über die rechte Kante)
|
||||
--mock-ar 1.6 (16:10)
|
||||
--mock-left 92px
|
||||
```
|
||||
|
||||
**Regel: Das Format des Rahmens folgt dem Inhalt des Screenshots, nicht
|
||||
umgekehrt.** Der Form Builder ist eine schmale, zentrierte Spalte mit breiten
|
||||
leeren Rändern — in einem 16:10-Rahmen zeigt er vor allem Leere. Er bekommt
|
||||
deshalb 540 × 720 (3:4), mittig gesetzt, und einen Zoom von 3,8 direkt auf die
|
||||
Spalte.
|
||||
|
||||
### Screenshots zuschneiden
|
||||
|
||||
Quellen sind ~2000 px breit und im Vollbild unlesbar. Jeder Screen wird auf
|
||||
seinen aussagekräftigen Bereich gezoomt. Die Werte sind gerechnet, nicht geraten:
|
||||
|
||||
```
|
||||
Rahmenbreite = 1 Einheit, Bild bei scale 1 = 1 Einheit breit
|
||||
Bildhöhe = 1/BildAR Rahmenhöhe = 1/RahmenAR
|
||||
|
||||
Punkt fx horizontal mittig: tx = 0.5/s − fx
|
||||
Punkt fy vertikal mittig: ty = (RahmenH/2)/(s · BildH) − fy
|
||||
```
|
||||
|
||||
Mindest-Zoom zum Füllen: `RahmenH / BildH`. Darunter entstehen Lücken.
|
||||
|
||||
**Ausnahme Titel-Slide:** Der Laptop zeigt den Screen **1:1**, ohne Zoom. Dort
|
||||
soll ein Bildschirm zeigen, was ein Bildschirm zeigt. Die Displayfläche misst
|
||||
739 × 489 (AR 1,511), `workflow.png` hat 1,504 — passt praktisch deckungsgleich.
|
||||
|
||||
### Neue Geräte-Mockups einbauen
|
||||
|
||||
1. Braucht **echte Transparenz**. Ein Screenshot eines transparenten PNGs ist
|
||||
keine Transparenz — das Karomuster steckt dann als Pixel drin und lässt sich
|
||||
nicht sauber entfernen (einmal versucht, Ergebnis unbrauchbar)
|
||||
2. Ist die Displayfläche deckend, muss sie **ausgeknockt** werden, damit der
|
||||
Screenshot dahinter liegen kann. Nur so bleiben Notch und Rundungen oben
|
||||
3. Displayfläche ausmessen und als Prozentwerte eintragen — sie hängt am
|
||||
konkreten Bild
|
||||
4. Schatten über `filter: drop-shadow()` auf dem Container, nicht im PNG
|
||||
|
||||
---
|
||||
|
||||
## 7. Export & Bildqualität
|
||||
|
||||
### Die zwei Regeln, aus denen alles folgt
|
||||
|
||||
**1. Genau 1080 px breit hochladen.** Instagram behält bei Breiten von 320 bis
|
||||
1080 die Originalauflösung. Alles darüber wird **von Instagram selbst**
|
||||
verkleinert und danach neu komprimiert — Qualitätsverlust in zwei Runden statt
|
||||
einer. Ein 4000-px-Upload ist deshalb schlechter als ein 1080-px-Upload, nicht
|
||||
besser.
|
||||
|
||||
**2. Trotzdem größer rendern.** Wir rastern intern bei **2×** (2160 × 2700) und
|
||||
skalieren selbst auf exakt 1080 × 1350 herunter. Das ist kein Widerspruch zu
|
||||
Regel 1, sondern der eigentliche Trick: Wir kontrollieren das Downsampling,
|
||||
statt es Instagram zu überlassen.
|
||||
|
||||
```
|
||||
2160 × 2700 → Halbierung → 1080 × 1350 → Upload
|
||||
render exakt
|
||||
```
|
||||
|
||||
### Warum das Supersampling hier hilft
|
||||
|
||||
Nicht wegen des Textes — den rastert der Browser bei nativer Größe ohnehin
|
||||
sauber, teils sogar schärfer als supersampled. Der Gewinn liegt woanders:
|
||||
|
||||
- **Transformationen** — der gekippte Laptop, gedrehte Formen, Chamfer-Kanten
|
||||
und Hexagon-Clips werden bei doppelter Auflösung gerastert und danach
|
||||
gemittelt. Das ist echtes Anti-Aliasing statt Treppenkanten
|
||||
- **Screenshots** — unsere Quellen sind ~2000 px breit und werden auf ~1090 px
|
||||
gezeigt. Bei 2× zeichnet der Browser sie näher an ihrer nativen Auflösung, das
|
||||
anschließende Downsampling ist unser eigenes und damit besser
|
||||
- **Marker und Wabenraster** — SVG-Kanten profitieren identisch
|
||||
|
||||
> Bei reinen Textslides ohne Mockup ist der Unterschied gering bis null. Deshalb
|
||||
> ist die Stufe umschaltbar (1× / 2× / 3×), nicht fest verdrahtet. **2× ist der
|
||||
> Standard**, 3× kostet spürbar Zeit und Speicher bei kaum sichtbarem Gewinn.
|
||||
|
||||
### Warum stufenweises Halbieren
|
||||
|
||||
Browser skalieren Canvas standardmäßig schnell statt gut: Pixel werden direkt
|
||||
übernommen, Nachbarinformationen gehen verloren, das Ergebnis rauscht. Zwei
|
||||
Gegenmaßnahmen, beide nötig:
|
||||
|
||||
```js
|
||||
ctx.imageSmoothingEnabled = true;
|
||||
ctx.imageSmoothingQuality = "high"; // Default ist "low"
|
||||
```
|
||||
|
||||
Und: **in Halbierungsschritten** verkleinern statt in einem Sprung. Ein direkter
|
||||
Sprung von 3240 auf 1080 überspringt Zwischenpixel; zweimal halbieren mittelt
|
||||
jeweils über 2 × 2 und kommt einer echten Flächenmittelung nahe.
|
||||
|
||||
`createImageBitmap()` mit `resizeQuality` wäre der elegantere Weg, ist aber in
|
||||
Browsern uneinheitlich umgesetzt — teils wird die Option ignoriert. `drawImage`
|
||||
mit `imageSmoothingQuality` ist der verlässliche Pfad.
|
||||
|
||||
### Format
|
||||
|
||||
**PNG, nicht JPEG.** Instagram konvertiert am Ende ohnehin zu JPEG — aber unsere
|
||||
Slides sind Text, flache Flächen und UI-Screenshots mit harten Kanten. Genau die
|
||||
zerlegt JPEG-Kompression zuerst. Wer als PNG hochlädt, gibt Instagram ein
|
||||
verlustfreies Original und zahlt nur **einmal** Kompression statt zweimal.
|
||||
|
||||
Für Fotoinhalte wäre JPEG bei 85–95 % die bessere Wahl. Haben wir hier nicht.
|
||||
|
||||
### Farbraum
|
||||
|
||||
**sRGB, ausschließlich.** Instagram wirft andere Profile weg und konvertiert
|
||||
nach sRGB. Wer in Adobe RGB oder P3 exportiert, bekommt Farbverschiebungen bei
|
||||
dieser Konvertierung. Canvas-Exporte aus dem Browser sind standardmäßig sRGB —
|
||||
also nichts zu tun, solange niemand ein Farbprofil anfasst.
|
||||
|
||||
### Weiteres
|
||||
|
||||
- Auf `document.fonts.ready` warten, sonst rendert der erste Export in einer
|
||||
Fallback-Schrift
|
||||
- Dateinamen mit `01-`…`05-` präfixen: Instagram übernimmt beim Hochladen die
|
||||
Dateisortierung
|
||||
- Dateigröße im Auge behalten. Für JPEG gilt die Faustregel „unter 1 MB", sonst
|
||||
komprimiert Instagram härter. Für unsere PNGs mit Screenshots ist das nicht zu
|
||||
halten und auch nicht nötig
|
||||
- Vom Desktop hochladen, wenn möglich — gilt als schonender als der mobile Upload
|
||||
|
||||
### Quellenlage, ehrlich
|
||||
|
||||
**Von Meta dokumentiert:** die unterstützten Seitenverhältnisse (1.91:1 bis 4:5),
|
||||
die Auflösungsgrenze 320–1080, der Beschnitt außerhalb, und dass alle
|
||||
Carousel-Slides auf die erste beschnitten werden.
|
||||
|
||||
**Community-Konsens, nicht offiziell:** die konkreten Kompressionsprozente, die
|
||||
1-MB-Faustregel, Desktop-vs-Mobile, und PNG-vs-JPEG-Empfehlungen. Plausibel und
|
||||
breit reproduziert, aber von Meta nirgends bestätigt. Im Zweifel selbst
|
||||
gegentesten: dieselbe Slide in zwei Varianten hochladen und vergleichen.
|
||||
|
||||
### Vier Dinge, die den Export brechen
|
||||
|
||||
**1. Tailwind- und `oklch()`-Farben.** Die Export-Bibliothek kann sie nicht
|
||||
serialisieren. Der Slide-Baum benutzt deshalb ausschließlich Hex und `rgba()`.
|
||||
|
||||
**2. PNGs im `foreignObject`.** Der Export serialisiert die Slide in eine
|
||||
`data:image/svg+xml`-URL und malt sie als **ein** Bild. Raster-Kinder dekodieren
|
||||
asynchron und verpassen diesen Paint regelmäßig — der Screenshot fehlt dann,
|
||||
während der Rest stimmt. SVGs dekodieren synchron und sind immer da.
|
||||
|
||||
> Deshalb ist der Export **verifiziert statt gehofft**: Jede Screenshot-Fläche
|
||||
> wird nach dem Rendern abgetastet. Eine solche Fläche ist nie glatt, sie trägt
|
||||
> UI und Farbe. Liegt die Luminanz-Spanne unter 12, fehlt das Bild und es wird
|
||||
> neu gerendert, bis zu fünf Versuche.
|
||||
|
||||
**3. Browser-Downloads.** `data:`-URLs öffneten einen leeren Tab, `blob:`-URLs
|
||||
das Bild im Tab — das `download`-Attribut wurde beide Male ignoriert. Der
|
||||
Generator schickt die Bytes deshalb per POST an **`/save`**, und `serve.py`
|
||||
schreibt direkt nach `~/Downloads`.
|
||||
|
||||
**4. Stille Skriptfehler.** Ein Fehler auf oberster Ebene (Syntax, Temporal Dead
|
||||
Zone) tötet das ganze Skript: Das Panel rendert aus statischem HTML weiter, aber
|
||||
nichts wird gebaut. Beide Seiten haben einen globalen `error`-Listener, der so
|
||||
etwas ins Statusfeld schreibt. **Nicht entfernen.**
|
||||
|
||||
---
|
||||
|
||||
## 8. Fallen, in die wir schon getappt sind
|
||||
|
||||
| Symptom | Ursache |
|
||||
|---|---|
|
||||
| Logo unsichtbar auf Weiß | Sichtbarkeitsregel vertauscht — auf Hell wurde `logo-white.svg` gezeigt |
|
||||
| Logo bildschirmbreit statt zentriert | `.laptop-screen img` galt auch fürs Logo und hat es überschrieben. Solche Regeln **immer auf eine Klasse eingrenzen** |
|
||||
| Element eine Zeile zu tief | `max-width` erzwang einen zusätzlichen Umbruch. Bei gesetzten `<br>` gehört `white-space: nowrap` dazu |
|
||||
| Farbe „stimmt nicht" | Deckkraft der Ebene lag darüber. Eine Farbe ist nur exakt, wenn nichts sie multipliziert |
|
||||
| Muster wirkt zufällig hingeworfen | Formen liefen quer durch den Text. Dekoration bleibt in den Rändern, verankert und angeschnitten |
|
||||
| Nichts lädt, Bühne leer | Konstante vor ihrer Deklaration benutzt (TDZ) |
|
||||
|
||||
---
|
||||
|
||||
## 9. Ordner
|
||||
|
||||
```
|
||||
posts/
|
||||
├── serve.py / serve.sh Statischer Server + /save-Endpunkt (Port 4321)
|
||||
├── post1.html Post 1 · Lösung (Carousel, 5 Slides)
|
||||
├── index.html Post 2 · Statement (Einzelbild)
|
||||
├── rules.md diese Datei
|
||||
├── assets/
|
||||
│ ├── logo.svg, logo-white.svg, cta-honeycomb.svg
|
||||
│ ├── laptop.png Mockup, Display ausgeknockt
|
||||
│ ├── fonts/ Manrope, lokal
|
||||
│ └── screens/ Software-Screenshots
|
||||
└── vendor/ html-to-image, JSZip (lokal, kein CDN)
|
||||
```
|
||||
|
||||
Alles läuft **offline**. Keine CDN-Verweise, keine Google-Fonts zur Laufzeit —
|
||||
sonst hängt der Export an einer Netzwerkverbindung und an fremden Origins.
|
||||
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Static server for the post generator, plus a save endpoint.
|
||||
|
||||
The browser download route turned out to be unreliable here: with a data: URL it
|
||||
opened a blank tab, with a blob: URL it opened the image in a tab. Both times the
|
||||
download attribute was ignored. Since this only ever runs locally, the generator
|
||||
now POSTs the rendered bytes to /save and the file is written straight to the
|
||||
Downloads folder. No browser download machinery involved.
|
||||
"""
|
||||
|
||||
import http.server
|
||||
import os
|
||||
import re
|
||||
import socketserver
|
||||
from pathlib import Path
|
||||
|
||||
PORT = 4321
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
TARGET = Path.home() / "Downloads"
|
||||
SAFE_NAME = re.compile(r"^[A-Za-z0-9._-]{1,120}$")
|
||||
|
||||
|
||||
class Handler(http.server.SimpleHTTPRequestHandler):
|
||||
def __init__(self, *a, **kw):
|
||||
super().__init__(*a, directory=str(ROOT), **kw)
|
||||
|
||||
def do_POST(self):
|
||||
if self.path.split("?")[0] != "/save":
|
||||
self.send_error(404)
|
||||
return
|
||||
|
||||
name = self.headers.get("X-Filename", "")
|
||||
# The filename comes from the page, so it is never trusted: no paths, no
|
||||
# traversal, plain basename only.
|
||||
name = os.path.basename(name)
|
||||
if not SAFE_NAME.match(name):
|
||||
self._json(400, '{"error":"ungueltiger Dateiname"}')
|
||||
return
|
||||
|
||||
length = int(self.headers.get("Content-Length", 0))
|
||||
if length <= 0 or length > 80 * 1024 * 1024:
|
||||
self._json(400, '{"error":"ungueltige Groesse"}')
|
||||
return
|
||||
|
||||
data = self.rfile.read(length)
|
||||
TARGET.mkdir(parents=True, exist_ok=True)
|
||||
dest = TARGET / name
|
||||
stem, suffix = dest.stem, dest.suffix
|
||||
n = 1
|
||||
while dest.exists():
|
||||
dest = TARGET / f"{stem}-{n}{suffix}"
|
||||
n += 1
|
||||
dest.write_bytes(data)
|
||||
print(f"gespeichert: {dest} ({len(data)/1024:.0f} KB)")
|
||||
self._json(200, '{"path":"%s"}' % dest)
|
||||
|
||||
def _json(self, code, body):
|
||||
payload = body.encode()
|
||||
self.send_response(code)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Content-Length", str(len(payload)))
|
||||
self.end_headers()
|
||||
self.wfile.write(payload)
|
||||
|
||||
def log_message(self, fmt, *args):
|
||||
if "POST" in (args[0] if args else ""):
|
||||
super().log_message(fmt, *args)
|
||||
|
||||
|
||||
class Server(socketserver.ThreadingTCPServer):
|
||||
allow_reuse_address = True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(f"Generator: http://127.0.0.1:{PORT}/post1.html")
|
||||
print(f"Speichert nach: {TARGET}")
|
||||
with Server(("127.0.0.1", PORT), Handler) as httpd:
|
||||
httpd.serve_forever()
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
# Serve the generator on a free port, with the /save endpoint.
|
||||
cd "$(dirname "$0")" && exec python3 serve.py
|
||||
@@ -0,0 +1,2 @@
|
||||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).htmlToImage={})}(this,(function(t){"use strict";function e(t,e,n,r){return new(n||(n=Promise))((function(i,o){function u(t){try{a(r.next(t))}catch(t){o(t)}}function c(t){try{a(r.throw(t))}catch(t){o(t)}}function a(t){var e;t.done?i(t.value):(e=t.value,e instanceof n?e:new n((function(t){t(e)}))).then(u,c)}a((r=r.apply(t,e||[])).next())}))}function n(t,e){var n,r,i,o,u={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:c(0),throw:c(1),return:c(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function c(c){return function(a){return function(c){if(n)throw new TypeError("Generator is already executing.");for(;o&&(o=0,c[0]&&(u=0)),u;)try{if(n=1,r&&(i=2&c[0]?r.return:c[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,c[1])).done)return i;switch(r=0,i&&(c=[2&c[0],i.value]),c[0]){case 0:case 1:i=c;break;case 4:return u.label++,{value:c[1],done:!1};case 5:u.label++,r=c[1],c=[0];continue;case 7:c=u.ops.pop(),u.trys.pop();continue;default:if(!(i=u.trys,(i=i.length>0&&i[i.length-1])||6!==c[0]&&2!==c[0])){u=0;continue}if(3===c[0]&&(!i||c[1]>i[0]&&c[1]<i[3])){u.label=c[1];break}if(6===c[0]&&u.label<i[1]){u.label=i[1],i=c;break}if(i&&u.label<i[2]){u.label=i[2],u.ops.push(c);break}i[2]&&u.ops.pop(),u.trys.pop();continue}c=e.call(t,u)}catch(t){c=[6,t],r=0}finally{n=i=0}if(5&c[0])throw c[1];return{value:c[0]?c[1]:void 0,done:!0}}([c,a])}}}var r,i=(r=0,function(){return r+=1,"u".concat("0000".concat((Math.random()*Math.pow(36,4)<<0).toString(36)).slice(-4)).concat(r)});function o(t){for(var e=[],n=0,r=t.length;n<r;n++)e.push(t[n]);return e}var u=null;function c(t){return void 0===t&&(t={}),u||(u=t.includeStyleProperties?t.includeStyleProperties:o(window.getComputedStyle(document.documentElement)))}function a(t,e){var n=(t.ownerDocument.defaultView||window).getComputedStyle(t).getPropertyValue(e);return n?parseFloat(n.replace("px","")):0}function s(t,e){void 0===e&&(e={});var n,r,i,o=e.width||(r=a(n=t,"border-left-width"),i=a(n,"border-right-width"),n.clientWidth+r+i),u=e.height||function(t){var e=a(t,"border-top-width"),n=a(t,"border-bottom-width");return t.clientHeight+e+n}(t);return{width:o,height:u}}var l=16384;function f(t,e){return void 0===e&&(e={}),t.toBlob?new Promise((function(n){t.toBlob(n,e.type?e.type:"image/png",e.quality?e.quality:1)})):new Promise((function(n){for(var r=window.atob(t.toDataURL(e.type?e.type:void 0,e.quality?e.quality:void 0).split(",")[1]),i=r.length,o=new Uint8Array(i),u=0;u<i;u+=1)o[u]=r.charCodeAt(u);n(new Blob([o],{type:e.type?e.type:"image/png"}))}))}function h(t){return new Promise((function(e,n){var r=new Image;r.onload=function(){r.decode().then((function(){requestAnimationFrame((function(){return e(r)}))}))},r.onerror=n,r.crossOrigin="anonymous",r.decoding="async",r.src=t}))}function d(t){return e(this,void 0,void 0,(function(){return n(this,(function(e){return[2,Promise.resolve().then((function(){return(new XMLSerializer).serializeToString(t)})).then(encodeURIComponent).then((function(t){return"data:image/svg+xml;charset=utf-8,".concat(t)}))]}))}))}function v(t,r,i){return e(this,void 0,void 0,(function(){var e,o,u;return n(this,(function(n){return e="http://www.w3.org/2000/svg",o=document.createElementNS(e,"svg"),u=document.createElementNS(e,"foreignObject"),o.setAttribute("width","".concat(r)),o.setAttribute("height","".concat(i)),o.setAttribute("viewBox","0 0 ".concat(r," ").concat(i)),u.setAttribute("width","100%"),u.setAttribute("height","100%"),u.setAttribute("x","0"),u.setAttribute("y","0"),u.setAttribute("externalResourcesRequired","true"),o.appendChild(u),u.appendChild(t),[2,d(o)]}))}))}var p=function(t,e){if(t instanceof e)return!0;var n=Object.getPrototypeOf(t);return null!==n&&(n.constructor.name===e.name||p(n,e))};function g(t,e,n,r){var i=".".concat(t,":").concat(e),o=n.cssText?function(t){var e=t.getPropertyValue("content");return"".concat(t.cssText," content: '").concat(e.replace(/'|"/g,""),"';")}(n):function(t,e){return c(e).map((function(e){var n=t.getPropertyValue(e),r=t.getPropertyPriority(e);return"".concat(e,": ").concat(n).concat(r?" !important":"",";")})).join(" ")}(n,r);return document.createTextNode("".concat(i,"{").concat(o,"}"))}function m(t,e,n,r){var o=window.getComputedStyle(t,n),u=o.getPropertyValue("content");if(""!==u&&"none"!==u){var c=i();try{e.className="".concat(e.className," ").concat(c)}catch(t){return}var a=document.createElement("style");a.appendChild(g(c,n,o,r)),e.appendChild(a)}}var w="application/font-woff",y="image/jpeg",b={woff:w,woff2:w,ttf:"application/font-truetype",eot:"application/vnd.ms-fontobject",png:"image/png",jpg:y,jpeg:y,gif:"image/gif",tiff:"image/tiff",svg:"image/svg+xml",webp:"image/webp"};function S(t){var e=function(t){var e=/\.([^./]*?)$/g.exec(t);rLine truncated
|
||||
//# sourceMappingURL=html-to-image.js.map
|
||||
@@ -0,0 +1,13 @@
|
||||
/*!
|
||||
|
||||
JSZip v3.10.1 - A JavaScript class for generating and reading zip files
|
||||
<http://stuartk.com/jszip>
|
||||
|
||||
(c) 2009-2016 Stuart Knightley <stuart [at] stuartk.com>
|
||||
Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/main/LICENSE.markdown.
|
||||
|
||||
JSZip uses the library pako released under the MIT license :
|
||||
https://github.com/nodeca/pako/blob/main/LICENSE
|
||||
*/
|
||||
|
||||
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).JSZip=e()}}(function(){return function s(a,o,h){function u(r,e){if(!o[r]){if(!a[r]){var t="function"==typeof require&&require;if(!e&&t)return t(r,!0);if(l)return l(r,!0);var n=new Error("Cannot find module '"+r+"'");throw n.code="MODULE_NOT_FOUND",n}var i=o[r]={exports:{}};a[r][0].call(i.exports,function(e){var t=a[r][1][e];return u(t||e)},i,i.exports,s,a,o,h)}return o[r].exports}for(var l="function"==typeof require&&require,e=0;e<h.length;e++)u(h[e]);return u}({1:[function(e,t,r){"use strict";var d=e("./utils"),c=e("./support"),p="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";r.encode=function(e){for(var t,r,n,i,s,a,o,h=[],u=0,l=e.length,f=l,c="string"!==d.getTypeOf(e);u<e.length;)f=l-u,n=c?(t=e[u++],r=u<l?e[u++]:0,u<l?e[u++]:0):(t=e.charCodeAt(u++),r=u<l?e.charCodeAt(u++):0,u<l?e.charCodeAt(u++):0),i=t>>2,s=(3&t)<<4|r>>4,a=1<f?(15&r)<<2|n>>6:64,o=2<f?63&n:64,h.push(p.charAt(i)+p.charAt(s)+p.charAt(a)+p.charAt(o));return h.join("")},r.decode=function(e){var t,r,n,i,s,a,o=0,h=0,u="data:";if(e.substr(0,u.length)===u)throw new Error("Invalid base64 input, it looks like a data url.");var l,f=3*(e=e.replace(/[^A-Za-z0-9+/=]/g,"")).length/4;if(e.charAt(e.length-1)===p.charAt(64)&&f--,e.charAt(e.length-2)===p.charAt(64)&&f--,f%1!=0)throw new Error("Invalid base64 input, bad content length.");for(l=c.uint8array?new Uint8Array(0|f):new Array(0|f);o<e.length;)t=p.indexOf(e.charAt(o++))<<2|(i=p.indexOf(e.charAt(o++)))>>4,r=(15&i)<<4|(s=p.indexOf(e.charAt(o++)))>>2,n=(3&s)<<6|(a=p.indexOf(e.charAt(o++))),l[h++]=t,64!==s&&(l[h++]=r),64!==a&&(l[h++]=n);return l}},{"./support":30,"./utils":32}],2:[function(e,t,r){"use strict";var n=e("./external"),i=e("./stream/DataWorker"),s=e("./stream/Crc32Probe"),a=e("./stream/DataLengthProbe");function o(e,t,r,n,i){this.compressedSize=e,this.uncompressedSize=t,this.crc32=r,this.compression=n,this.compressedContent=i}o.prototype={getContentWorker:function(){var e=new i(n.Promise.resolve(this.compressedContent)).pipe(this.compression.uncompressWorker()).pipe(new a("data_length")),t=this;return e.on("end",function(){if(this.streamInfo.data_length!==t.uncompressedSize)throw new Error("Bug : uncompressed data size mismatch")}),e},getCompressedWorker:function(){return new i(n.Promise.resolve(this.compressedContent)).withStreamInfo("compressedSize",this.compressedSize).withStreamInfo("uncompressedSize",this.uncompressedSize).withStreamInfo("crc32",this.crc32).withStreamInfo("compression",this.compression)}},o.createWorkerFrom=function(e,t,r){return e.pipe(new s).pipe(new a("uncompressedSize")).pipe(t.compressWorker(r)).pipe(new a("compressedSize")).withStreamInfo("compression",t)},t.exports=o},{"./external":6,"./stream/Crc32Probe":25,"./stream/DataLengthProbe":26,"./stream/DataWorker":27}],3:[function(e,t,r){"use strict";var n=e("./stream/GenericWorker");r.STORE={magic:"\0\0",compressWorker:function(){return new n("STORE compression")},uncompressWorker:function(){return new n("STORE decompression")}},r.DEFLATE=e("./flate")},{"./flate":7,"./stream/GenericWorker":28}],4:[function(e,t,r){"use strict";var n=e("./utils");var o=function(){for(var e,t=[],r=0;r<256;r++){e=r;for(var n=0;n<8;n++)e=1&e?3988292384^e>>>1:e>>>1;t[r]=e}return t}();t.exports=function(e,t){return void 0!==e&&e.length?"string"!==n.getTypeOf(e)?function(e,t,r,n){var i=o,s=n+r;e^=-1;for(var a=n;a<s;a++)e=e>>>8^i[255&(e^t[a])];return-1^e}(0|t,e,e.length,0):function(e,t,r,n){var i=o,s=n+r;e^=-1;for(var a=n;a<s;a++)e=e>>>8^i[255&(e^t.charCodeAt(a))];return-1^e}(0|t,e,e.length,0):0}},{"./utils":32}],5:[function(e,t,r){"use strict";r.base64=!1,r.binary=!1,r.dir=!1,r.createFolders=!0,r.date=null,r.compression=null,r.compressionOptions=null,r.comment=null,r.unixPermissions=null,r.dosPermissions=null},{}],6:[function(e,t,r){"use strict";var n=null;n="undefined"!=typeof Promise?Promise:e("lie"),t.exports={Promise:n}},{lie:37}],7:[function(e,t,r){"use strict";var n="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Uint32Array,i=e("pako"),s=e("./utils"),a=e("./stream/GenericWorker"),o=n?"uint8array":"array";function h(e,t){a.call(this,"FlateWorker/"+e),this._pako=null,this._pakoAction=e,this._pakoOptions=t,this.meta={}}r.magic="\b\0",s.inherits(h,a),h.prototype.processChunk=function(e){this.meta=e.meta,null===this._pako&&this._createPako(),this._pako.push(s.transformTo(o,e.data),!1)},h.prototype.flush=function(){a.prototype.flush.call(this),null===this._pako&&this._createPako(),this._pako.push([],!0)},h.prototype.cleanUp=function(){a.prototype.cleanUp.call(this),this._pako=null},h.prototype._createPako=function(){this._pako=new i[this._pakoAction]({raw:!0,level:this._pakoOptions.level||-1});var t=this;this._pako.onData=function(Line truncated
|
||||