Redesign loader card as three load options

This commit is contained in:
Justin Oros
2026-09-21 22:32:53 -07:00
parent d2e66a79fc
commit fc70aa66fc
3 changed files with 136 additions and 75 deletions
+5 -3
View File
@@ -7,6 +7,7 @@
const $ = (id) => document.getElementById(id); const $ = (id) => document.getElementById(id);
const els = { const els = {
dropZone: $("dropZone"), dropZone: $("dropZone"),
dropTarget: $("dropTarget"),
loaderTitle: $("loaderTitle"), loaderTitle: $("loaderTitle"),
loaderStatus: $("loaderStatus"), loaderStatus: $("loaderStatus"),
browseBtn: $("browseBtn"), browseBtn: $("browseBtn"),
@@ -356,7 +357,7 @@
els.copyPathBtn.addEventListener("click", async () => { els.copyPathBtn.addEventListener("click", async () => {
try { try {
await navigator.clipboard.writeText(SAVES_PATH); await navigator.clipboard.writeText(SAVES_PATH);
toast("Saves folder path copied. Paste it into the file dialog's address bar, then open your SteamID and save folders."); toast("Path copied. Paste it into the file dialog's address bar, then open your SteamID and save folders.");
} catch { } catch {
toast("Couldn't copy. Select the path and copy it manually.", true); toast("Couldn't copy. Select the path and copy it manually.", true);
} }
@@ -393,8 +394,9 @@
els.dropZone.classList.remove("dragging"); els.dropZone.classList.remove("dragging");
loadFile(e.dataTransfer.files[0]); loadFile(e.dataTransfer.files[0]);
}); });
els.dropZone.addEventListener("keydown", (e) => { els.dropTarget.addEventListener("click", browse);
if (e.target === els.dropZone && (e.key === "Enter" || e.key === " ")) { els.dropTarget.addEventListener("keydown", (e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault(); e.preventDefault();
browse(); browse();
} }
+33 -15
View File
@@ -19,27 +19,45 @@
</header> </header>
<main> <main>
<section class="loader" id="dropZone" tabindex="0" aria-label="Load a save file"> <section class="loader" id="dropZone" aria-label="Load a save file">
<div class="loader-text"> <div class="loader-head">
<h2 id="loaderTitle">Drop your SaveData.zip here</h2> <h2 id="loaderTitle">Load your save</h2>
<p id="loaderStatus">No save loaded. You can browse and edit items, but saving needs a loaded save.</p> <p id="loaderStatus">No save loaded. You can browse and edit items, but saving needs a loaded save.</p>
</div> </div>
<div class="loader-actions">
<div class="options">
<div class="option">
<span class="option-tag">Option A</span>
<h3>Browse to your file</h3>
<p>Your save is here:</p>
<code class="path"><span>%USERPROFILE%\<wbr>AppData\<wbr>LocalLow\<wbr>Endnight\<wbr>SonsOfTheForest\<wbr>Saves\<wbr></span><var>SteamID</var><span>\<wbr>SinglePlayer\<wbr></span><var>SaveID</var><span>\<wbr>SaveData.zip</span></code>
<p class="hint"><var>SteamID</var> is the long number folder. Pick the newest <var>SaveID</var> folder. Co-op host saves are in Multiplayer.</p>
<div class="option-actions">
<button type="button" class="btn" id="browseBtn">Browse</button> <button type="button" class="btn" id="browseBtn">Browse</button>
<button type="button" class="btn-link copy-btn" id="copyPathBtn"><svg viewBox="0 0 16 16" width="13" height="13" aria-hidden="true"><rect x="5" y="5" width="9" height="9" rx="1.5" fill="none" stroke="currentColor" stroke-width="1.5"/><path d="M11 5V3.5A1.5 1.5 0 0 0 9.5 2h-6A1.5 1.5 0 0 0 2 3.5v6A1.5 1.5 0 0 0 3.5 11H5" fill="none" stroke="currentColor" stroke-width="1.5"/></svg><span>Copy path</span></button>
</div>
<input type="file" id="fileInput" accept=".zip,.json" hidden> <input type="file" id="fileInput" accept=".zip,.json" hidden>
</div> </div>
<div class="path-hint">
<span class="path-label">Your save file is here <div class="option">
<button type="button" class="btn-link copy-btn" id="copyPathBtn"><svg viewBox="0 0 16 16" width="13" height="13" aria-hidden="true"><rect x="5" y="5" width="9" height="9" rx="1.5" fill="none" stroke="currentColor" stroke-width="1.5"/><path d="M11 5V3.5A1.5 1.5 0 0 0 9.5 2h-6A1.5 1.5 0 0 0 2 3.5v6A1.5 1.5 0 0 0 3.5 11H5" fill="none" stroke="currentColor" stroke-width="1.5"/></svg><span>Copy Path</span></button> <span class="option-tag">Option B</span>
</span> <h3>Use this PowerShell script</h3>
<code class="path"><span>%USERPROFILE%\AppData\LocalLow\Endnight\SonsOfTheForest\Saves\</span><var>SteamID</var><span>\SinglePlayer\</span><var>SaveID</var><span>\SaveData.zip</span></code> <p>Close the game, open PowerShell and paste:</p>
<p class="path-note"><var>SteamID</var> is the long number folder. <var>SaveID</var> is one folder per save; sort by date modified to find your latest. Hosted co-op saves are under Multiplayer folder instead of SinglePlayer.</p> <code class="path" id="helperCmd">irm https://<wbr>raw.githubusercontent.com/<wbr>JustinOros/<wbr>sotf-inventory/<wbr>main/<wbr>Find-SaveData.ps1 | iex</code>
<p class="path-note">Or let PowerShell find it for you. Close the game, open PowerShell and paste:</p> <p class="hint">Explorer opens with your latest SaveData.zip selected. Drag it onto this page.</p>
<div class="cmd"> <div class="option-actions">
<code class="path" id="helperCmd">irm https://raw.githubusercontent.com/JustinOros/sotf-inventory/main/Find-SaveData.ps1 | iex</code> <button type="button" class="btn copy-btn" id="copyCmdBtn"><svg viewBox="0 0 16 16" width="13" height="13" aria-hidden="true"><rect x="5" y="5" width="9" height="9" rx="1.5" fill="none" stroke="currentColor" stroke-width="1.5"/><path d="M11 5V3.5A1.5 1.5 0 0 0 9.5 2h-6A1.5 1.5 0 0 0 2 3.5v6A1.5 1.5 0 0 0 3.5 11H5" fill="none" stroke="currentColor" stroke-width="1.5"/></svg><span>Copy command</span></button>
<button type="button" class="btn-link copy-btn" id="copyCmdBtn"><svg viewBox="0 0 16 16" width="13" height="13" aria-hidden="true"><rect x="5" y="5" width="9" height="9" rx="1.5" fill="none" stroke="currentColor" stroke-width="1.5"/><path d="M11 5V3.5A1.5 1.5 0 0 0 9.5 2h-6A1.5 1.5 0 0 0 2 3.5v6A1.5 1.5 0 0 0 3.5 11H5" fill="none" stroke="currentColor" stroke-width="1.5"/></svg><span>Copy</span></button> </div>
</div>
<div class="option option-drop" id="dropTarget" tabindex="0" role="button" aria-label="Drop SaveData.zip here or press Enter to browse">
<span class="option-tag">Option C</span>
<h3>Drag and drop</h3>
<div class="drop-well">
<svg viewBox="0 0 24 24" width="36" height="36" aria-hidden="true"><path d="M12 3v12m0 0-4.5-4.5M12 15l4.5-4.5M4 17v2.5A1.5 1.5 0 0 0 5.5 21h13a1.5 1.5 0 0 0 1.5-1.5V17" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span>Drop your SaveData.zip here</span>
</div>
</div> </div>
<p class="path-note">It opens Explorer with your latest SaveData.zip selected, ready to drag onto this page.</p>
</div> </div>
</section> </section>
+95 -54
View File
@@ -77,106 +77,146 @@ main {
} }
.loader { .loader {
display: grid; padding: 1.75rem;
grid-template-columns: 1fr auto; border: 1px solid var(--line);
gap: 1rem 1.5rem;
align-items: center;
padding: 1.5rem;
border: 1px dashed var(--moss);
border-radius: 4px; border-radius: 4px;
background: var(--spruce-2); background: var(--spruce-2);
transition: border-color 0.15s, background 0.15s;
} }
.loader.dragging { .loader-head h2 {
border-color: var(--bone);
border-style: solid;
background: var(--spruce-3);
}
.loader.loaded {
border-style: solid;
border-color: var(--line);
}
.loader h2 {
font-family: var(--display); font-family: var(--display);
font-weight: 400; font-weight: 400;
font-size: 1.75rem; font-size: 1.9rem;
margin: 0; margin: 0;
} }
.loader p { .loader-head p {
margin: 0.25rem 0 0; margin: 0.25rem 0 0;
color: var(--fog); color: var(--fog);
} }
.path-hint { .loader.loaded .loader-head h2 { color: #fff; }
grid-column: 1 / -1;
.options {
display: grid; display: grid;
gap: 0.4rem; grid-template-columns: repeat(3, minmax(0, 1fr));
justify-items: start; gap: 1.25rem;
font-size: 0.875rem; margin-top: 1.5rem;
}
.option {
display: flex;
flex-direction: column;
gap: 0.6rem;
padding: 1.25rem;
background: var(--spruce);
border: 1px solid var(--line);
border-radius: 4px;
}
.option-tag {
font-size: 0.8rem;
font-weight: 600;
color: var(--moss);
}
.option h3 {
font-family: var(--display);
font-weight: 400;
font-size: 1.4rem;
line-height: 1.2;
margin: -0.35rem 0 0.2rem;
}
.option p {
margin: 0;
color: var(--fog); color: var(--fog);
padding-top: 1rem; font-size: 0.925rem;
border-top: 1px solid var(--line); }
.option .hint {
font-size: 0.85rem;
line-height: 1.5;
}
.option-actions {
display: flex;
flex-wrap: wrap;
gap: 1rem;
align-items: center;
margin-top: auto;
padding-top: 0.4rem;
} }
.path { .path {
display: block; display: block;
width: 100%;
font-family: Consolas, "Courier New", monospace; font-family: Consolas, "Courier New", monospace;
font-size: 0.95rem; font-size: 0.8rem;
line-height: 1.55;
color: var(--bone); color: var(--bone);
background: var(--spruce); background: var(--spruce-2);
border: 1px solid var(--line); border: 1px solid var(--line);
border-radius: 3px; border-radius: 3px;
padding: 0.6rem 0.75rem; padding: 0.55rem 0.7rem;
overflow-wrap: anywhere; overflow-wrap: break-word;
user-select: all; user-select: all;
} }
.path-hint var { .option var {
font-style: normal; font-style: normal;
color: #e0a39a; color: #e0a39a;
border-bottom: 1px dotted var(--blood-hi);
}
.path-label {
display: flex;
align-items: baseline;
gap: 0.35rem;
} }
.copy-btn { .copy-btn {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 0.3rem; gap: 0.4rem;
padding: 0;
text-decoration: none; text-decoration: none;
} }
.copy-btn span { .btn-link.copy-btn { padding: 0; }
.copy-btn svg { flex: none; }
.btn.copy-btn {
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.btn-link.copy-btn span {
text-decoration: underline; text-decoration: underline;
text-underline-offset: 3px; text-underline-offset: 3px;
} }
.copy-btn svg { position: relative; top: 1px; } .option-drop {
cursor: pointer;
}
.cmd { .drop-well {
flex: 1;
display: flex; display: flex;
gap: 0.75rem; flex-direction: column;
align-items: center; align-items: center;
width: 100%; justify-content: center;
gap: 0.75rem;
min-height: 11rem;
text-align: center;
color: var(--fog);
border: 2px dashed var(--moss);
border-radius: 4px;
transition: border-color 0.15s, background 0.15s, color 0.15s;
} }
.cmd .path { flex: 1; } .option-drop:hover .drop-well,
.loader.dragging .drop-well {
.path-note { border-color: var(--bone);
margin: 0; color: var(--bone);
max-width: 60em; background: var(--spruce-3);
} }
.loader.dragging { border-color: var(--moss); }
.btn { .btn {
font: 600 1rem var(--body); font: 600 1rem var(--body);
color: var(--bone); color: var(--bone);
@@ -469,7 +509,8 @@ dialog p { margin: 0 0 0.9rem; line-height: 1.55; }
@media (max-width: 720px) { @media (max-width: 720px) {
.hero { padding: 2.5rem 1rem 5.5rem; } .hero { padding: 2.5rem 1rem 5.5rem; }
main { padding: 1.5rem 1rem 0; } main { padding: 1.5rem 1rem 0; }
.loader { grid-template-columns: 1fr; } .options { grid-template-columns: 1fr; }
.drop-well { min-height: 7rem; }
.col-cat, .cell-cat, .col-id, .cell-id { display: none; } .col-cat, .cell-cat, .col-id, .cell-id { display: none; }
.col-count { width: auto; } .col-count { width: auto; }
.stepper input { width: 4rem; } .stepper input { width: 4rem; }