Skip to content

Commit 6374a68

Browse files
authored
Presets
1 parent 05ee657 commit 6374a68

File tree

1 file changed

+318
-2
lines changed

1 file changed

+318
-2
lines changed

index.html

Lines changed: 318 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<html lang="en">
33
<head>
44
<meta charset="UTF-8">
5-
<title>blobSketch version 1.11</title>
5+
<title>blobSketch version 1.12</title>
66
<style>
77
/* ───────── 1. Base / Reset ───────── */
88
body {
@@ -475,6 +475,50 @@
475475
box-shadow:0 0 8px 3px #ffe600 !important;
476476
}
477477

478+
/* --- prettier Presets gallery ---------------------------------- */
479+
.presets-gallery{
480+
display:grid;
481+
grid-template-columns: repeat(auto-fill, minmax(120px,1fr));
482+
gap:12px;
483+
padding:4px;
484+
max-height:60vh; /* scroll if many presets */
485+
overflow-y:auto;
486+
}
487+
488+
.preset-card{
489+
aspect-ratio:1/1; /* always square */
490+
background:#fafafa;
491+
border:2px solid #000;
492+
box-shadow:3px 3px 0 #000;
493+
position:relative;
494+
transition:transform .15s, box-shadow .15s;
495+
}
496+
.preset-card:hover{
497+
transform:translateY(-3px);
498+
box-shadow:5px 5px 0 #000;
499+
}
500+
.preset-card:active{ transform:translateY(0); }
501+
502+
.preset-card img,
503+
.preset-card svg{ width:100%; height:100%; object-fit:cover; display:block; }
504+
505+
.preset-card figcaption{
506+
position:absolute; inset:0;
507+
background:rgba(0,0,0,.58);
508+
color:#fff; font:12px "Courier New",monospace;
509+
display:flex; align-items:center; justify-content:center;
510+
text-align:center; padding:4px;
511+
opacity:0; transition:opacity .15s;
512+
}
513+
.preset-card:hover figcaption{ opacity:1; }
514+
515+
/* highlight the card the user just applied */
516+
.preset-card.card-selected{
517+
outline:3px solid #ffe600;
518+
box-shadow:0 0 10px 3px #ffe600;
519+
transform:none;
520+
}
521+
478522
</style>
479523
</head>
480524
<body>
@@ -511,6 +555,7 @@
511555
<span id="exportFilledJPGOption">...Fill&gt;JPG</span>
512556
</div>
513557
</span>
558+
<span id="menuPresets">Presets</span>
514559
<span id="menuAbout">About</span>
515560
<span id="menuHelp">Help</span>
516561
</div>
@@ -655,10 +700,113 @@ <h4>Canvas</h4>
655700
</div>
656701
</div>
657702

703+
<!-- ░ PRESETS DIALOG ░ -->
704+
<div class="dialog-backdrop" id="presetsBackdrop">
705+
<div class="dialog-content presets-dialog">
706+
<h3>Select a Preset</h3>
707+
708+
<div id="presetsGallery" class="presets-gallery">
709+
<!-- Grid preset card -->
710+
<div class="preset-card" id="presetGrid">
711+
<svg viewBox="0 0 100 100" width="100%" height="100%">
712+
<rect width="100" height="100" fill="#fff"/>
713+
<g stroke="#000" stroke-width="2" fill="none">
714+
<!-- 5 × 5 grid preview -->
715+
<circle cx="10" cy="10" r="8"/>
716+
<circle cx="30" cy="10" r="8"/>
717+
<circle cx="50" cy="10" r="8"/>
718+
<circle cx="70" cy="10" r="8"/>
719+
<circle cx="90" cy="10" r="8"/>
720+
721+
<circle cx="10" cy="30" r="8"/>
722+
<circle cx="30" cy="30" r="8"/>
723+
<circle cx="50" cy="30" r="8"/>
724+
<circle cx="70" cy="30" r="8"/>
725+
<circle cx="90" cy="30" r="8"/>
726+
727+
<circle cx="10" cy="50" r="8"/>
728+
<circle cx="30" cy="50" r="8"/>
729+
<circle cx="50" cy="50" r="8"/>
730+
<circle cx="70" cy="50" r="8"/>
731+
<circle cx="90" cy="50" r="8"/>
732+
733+
<circle cx="10" cy="70" r="8"/>
734+
<circle cx="30" cy="70" r="8"/>
735+
<circle cx="50" cy="70" r="8"/>
736+
<circle cx="70" cy="70" r="8"/>
737+
<circle cx="90" cy="70" r="8"/>
738+
739+
<circle cx="10" cy="90" r="8"/>
740+
<circle cx="30" cy="90" r="8"/>
741+
<circle cx="50" cy="90" r="8"/>
742+
<circle cx="70" cy="90" r="8"/>
743+
<circle cx="90" cy="90" r="8"/>
744+
</g>
745+
</svg>
746+
<figcaption>Grid of Circles</figcaption>
747+
</div>
748+
<div class="preset-card" id="presetBlobGrid">
749+
<img src="images/grid_of_blobs.png" alt="Grid of Blobs">
750+
<figcaption>Grid of Blobs</figcaption>
751+
</div>
752+
</div>
753+
754+
<button id="closePresets">Close</button>
755+
</div>
756+
</div>
757+
758+
<div class="dialog-backdrop" id="gridBackdrop">
759+
<div class="dialog-content">
760+
<h3>Grid of Circles</h3>
761+
762+
<label>Count
763+
<input type="number" id="gridCount" value="5" min="1" max="12">
764+
</label><br>
765+
766+
<label>
767+
<input type="checkbox" id="autoRadius" checked>
768+
Fit circles to canvas
769+
</label><br>
770+
771+
<div id="radiusRow" style="display:none;">
772+
<label>Radius
773+
<input type="number" id="gridRadius" value="20" min="2" max="200">
774+
</label><br>
775+
</div>
776+
777+
<button id="applyGrid">Apply</button>
778+
<button id="cancelGrid">Cancel</button>
779+
</div>
780+
781+
</div>
782+
<div class="dialog-backdrop" id="blobGridBackdrop">
783+
<div class="dialog-content">
784+
<h3>Grid of Blobs</h3>
785+
786+
<label>Count (rows = cols)
787+
<input type="number" id="blobGridCount" value="5" min="1" max="10">
788+
</label><br>
789+
790+
<label>
791+
<input type="checkbox" id="blobAutoSize" checked>
792+
Fit blobs to canvas
793+
</label><br>
794+
795+
<div id="blobSizeRow" style="display:none;">
796+
<label>Base Radius
797+
<input type="number" id="blobBaseRadius" value="20" min="4" max="120">
798+
</label><br>
799+
</div>
800+
801+
<button id="applyBlobGrid">Apply</button>
802+
<button id="cancelBlobGrid">Cancel</button>
803+
</div>
804+
</div>
805+
658806
<!-- Modals/Dialogs: About and Help -->
659807
<div class="dialog-backdrop" id="aboutBackdrop">
660808
<div class="dialog-content">
661-
<p>blobSketch version 1.11</p>
809+
<p>blobSketch version 1.12</p>
662810
<p>by Colin Reid</p>
663811
<button id="closeAbout">OK</button>
664812
</div>
@@ -799,6 +947,174 @@ <h3>Import Options</h3>
799947
setupSubmenu("menuFile", "fileSubmenu");
800948
setupSubmenu("menuExport", "exportSubmenu");
801949

950+
951+
/* ---------- PRESETS ---------- */
952+
$("menuPresets").onclick = () => {
953+
$("presetsBackdrop").style.display = "flex";
954+
};
955+
956+
/* close gallery */
957+
$("closePresets").onclick = () =>
958+
$("presetsBackdrop").style.display = "none";
959+
960+
/* show the Grid-options dialog when the card is clicked */
961+
$("presetGrid").onclick = () => {
962+
$("presetsBackdrop").style.display = "none";
963+
$("gridBackdrop").style.display = "flex";
964+
};
965+
966+
/* cancel grid dialog */
967+
$("cancelGrid").onclick = () =>
968+
$("gridBackdrop").style.display = "none";
969+
970+
/* show / hide the custom-radius row */
971+
$("autoRadius").onchange = () =>
972+
$("radiusRow").style.display = $("autoRadius").checked ? "none" : "block";
973+
974+
/* apply grid */
975+
$("applyGrid").onclick = () => {
976+
const count = Math.min(12,
977+
Math.max(1, parseInt($("gridCount").value, 10) || 1));
978+
/* this radius would make circles kiss each other and the edges */
979+
const maxR = Math.min(canvas.width, canvas.height) / (count * 2);
980+
981+
/* the actual radius – either maxR or a user-supplied smaller one */
982+
const radius = $("autoRadius").checked
983+
? maxR
984+
: Math.min(parseFloat($("gridRadius").value) || 2, maxR);
985+
986+
/* IMPORTANT: spacing is based on maxR so the centres never move */
987+
const spacing = 2 * maxR;
988+
989+
for (let r = 0; r < count; r++) {
990+
for (let c = 0; c < count; c++) {
991+
circles.push({
992+
x: maxR + c * spacing,
993+
y: maxR + r * spacing,
994+
radius // may be < maxR ⇒ nice margin
995+
});
996+
history.push({ type: "circle" });
997+
}
998+
}
999+
1000+
/* set Blob Size slider to 0 % and Excitability to 85 % */
1001+
setDotScale(0); // updates UI + dotScaleFactor
1002+
damping = 0.85;
1003+
$("dampingSlider").value = 85;
1004+
$("dampingValue").textContent = "85%";
1005+
/* ── 1. High Repulsion ON ── */
1006+
if (interRepelMult !== 3) { // 1 = normal, 3 = high
1007+
interRepelMult = 3;
1008+
toggleActiveIcon($("iconDiffusion"), true);
1009+
}
1010+
1011+
/* ── 2. Blob draw-mode ── */
1012+
setDrawMode("blob"); // switches UI + cursor
1013+
1014+
/* ── 3. Edit tools OFF ── */
1015+
slicingMode = dragMode = pinMode =
1016+
freezeMode = deleteMode = false; // clear flags
1017+
$("editToolsSection").classList.remove("edit-on");
1018+
["iconSlice","iconDrag","iconPin","iconFreeze","iconDelete"]
1019+
.forEach(id=>setIconActive($(id),false,"",""));
1020+
updateCanvasCursor();
1021+
1022+
if (!solidFillMode) {
1023+
solidFillMode = true;
1024+
$("iconFill").title = "Solid Fill On";
1025+
toggleActiveIcon($("iconFill"), true);
1026+
}
1027+
1028+
toast(`Grid (${count}×${count}) added`, 1600);
1029+
$("gridBackdrop").style.display = "none";
1030+
};
1031+
1032+
1033+
/* === GRID-OF-BLOBS PRESET ======================================== */
1034+
$("presetBlobGrid").onclick = () => {
1035+
$("presetsBackdrop").style.display = "none"; // ✨ hide gallery
1036+
$("blobGridBackdrop").style.display = "flex"; // show blob-dialog
1037+
};
1038+
1039+
/* toggle custom size row */
1040+
$("blobAutoSize").onchange = () =>
1041+
$("blobSizeRow").style.display = $("blobAutoSize").checked ? "none" : "block";
1042+
1043+
/* helper to create a simple circular blob (you could swap in fancier math) */
1044+
function makeCircleBlob(cx, cy, r, colorArr) {
1045+
const steps = 48;
1046+
const pts = [];
1047+
for (let i = 0; i < steps; i++) {
1048+
const θ = i / steps * 2 * Math.PI;
1049+
pts.push({
1050+
x: cx + r * Math.cos(θ),
1051+
y: cy + r * Math.sin(θ),
1052+
vx: 0, vy: 0, pinned: false
1053+
});
1054+
}
1055+
return {
1056+
chain: pts,
1057+
color: colorArr,
1058+
baseDotRadius: r * 0.12,
1059+
isClosed: true,
1060+
frozen: false
1061+
};
1062+
}
1063+
1064+
/* Apply button */
1065+
$("applyBlobGrid").onclick = () => {
1066+
/* 1 ─ grid sizing */
1067+
const cnt = Math.min(10,
1068+
Math.max(1, parseInt($("blobGridCount").value, 10) || 1));
1069+
const maxR = Math.min(canvas.width, canvas.height) / (cnt * 2);
1070+
1071+
const baseR = $("blobAutoSize").checked
1072+
? maxR
1073+
: Math.min(parseFloat($("blobBaseRadius").value) || 4, maxR);
1074+
1075+
const spacing = 2 * maxR; // centres stay fixed
1076+
1077+
const chosenColor = hexToRGBA($("blobColor").value);
1078+
/* 2 ─ add blobs */
1079+
for (let r = 0; r < cnt; r++) {
1080+
for (let c = 0; c < cnt; c++) {
1081+
const cx = maxR + c * spacing;
1082+
const cy = maxR + r * spacing;
1083+
const blob = makeCircleBlob(cx, cy, baseR, chosenColor);
1084+
chains.push(blob);
1085+
history.push({ type: "chain" });
1086+
}
1087+
}
1088+
1089+
/* 3 ─ snap UI states exactly like circle-grid did */
1090+
{
1091+
if (interRepelMult !== 3) {
1092+
interRepelMult = 3;
1093+
toggleActiveIcon($("iconDiffusion"), true);
1094+
}
1095+
setDrawMode("blob");
1096+
$("dampingSlider").value = 40;
1097+
$("dampingValue").textContent = "40%";
1098+
setDotScale(12);
1099+
damping = 0.40;
1100+
if (!solidFillMode) {
1101+
solidFillMode = true;
1102+
$("iconFill").title = "Solid Fill On";
1103+
toggleActiveIcon($("iconFill"), true);
1104+
}
1105+
slicingMode = dragMode = pinMode = freezeMode = deleteMode = false;
1106+
$("editToolsSection").classList.remove("edit-on");
1107+
["iconSlice","iconDrag","iconPin","iconFreeze","iconDelete"]
1108+
.forEach(id=>setIconActive($(id),false,"",""));
1109+
updateCanvasCursor();
1110+
}
1111+
1112+
toast(`Blob grid (${cnt}×${cnt}) added`, 1600);
1113+
$("blobGridBackdrop").style.display = "none";
1114+
};
1115+
$("cancelBlobGrid").onclick = () =>
1116+
$("blobGridBackdrop").style.display = "none";
1117+
8021118
/* -----------------------------------
8031119
WEBGL CONTEXT
8041120
----------------------------------- */

0 commit comments

Comments
 (0)