Update Site source

This commit is contained in:
jzzliu
2026-11-07 20:13:33 -07:00
commit ca5c641d63
11 changed files with 158 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
{"project_id":"appgprj_6aefe80c1e848191a6ff4b0420d61c4e","static":{"directory":"dist"}}
+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 480 340">
<defs><linearGradient id="body" x1="0" x2="1" y1="0" y2="1"><stop stop-color="#9d6049"/><stop offset="1" stop-color="#243226" stop-opacity=".95"/></linearGradient><filter id="shadow"><feGaussianBlur stdDeviation="10"/></filter><filter id="grain"><feTurbulence type="fractalNoise" baseFrequency=".52" numOctaves="3" stitchTiles="stitch"/></filter></defs>
<rect width="480" height="340" fill="#e3cfbe"/><circle cx="242" cy="164" r="117" fill="#fff" opacity=".14"/><ellipse cx="240" cy="255" rx="108" ry="14" opacity=".25" filter="url(#shadow)" fill="#353830"/><path d="M115 166q8-29 37-28l168 17q36 4 39 39t-38 42l-164-17q-44-6-42-53" fill="url(#body)"/><path d="m164 140 11 81m9-79 11 82m70-73 11 81m9-79 11 81" stroke="#ddba85" stroke-width="7"/><ellipse cx="324" cy="196" rx="35" ry="40" fill="#854934"/><path d="M309 174c39-17 48 37 19 44-26 7-35-27-14-35 13-4 22 12 12 18" fill="none" stroke="#cf9b6a" stroke-width="5"/><path d="m174 213-6 16m11-14-4 18m13-17-4 18m11-16-4 18" stroke="#e1bf90" stroke-width="3"/><rect width="480" height="340" filter="url(#grain)" opacity=".15" style="mix-blend-mode:multiply"/><path d="M20 23h13m-13 0v13m440 281h-13m13 0v-13" stroke="#34483c" opacity=".35" fill="none"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 480 340">
<defs><linearGradient id="body" x1="0" x2="1" y1="0" y2="1"><stop stop-color="#718b8d"/><stop offset="1" stop-color="#243226" stop-opacity=".95"/></linearGradient><filter id="shadow"><feGaussianBlur stdDeviation="10"/></filter><filter id="grain"><feTurbulence type="fractalNoise" baseFrequency=".52" numOctaves="3" stitchTiles="stitch"/></filter></defs>
<rect width="480" height="340" fill="#d1d9d7"/><circle cx="242" cy="164" r="117" fill="#fff" opacity=".14"/><ellipse cx="240" cy="255" rx="108" ry="14" opacity=".25" filter="url(#shadow)" fill="#353830"/><path d="M223 81q0-23 20-23t20 23" stroke="#3c5150" stroke-width="9" fill="none"/><rect x="205" y="78" width="75" height="33" rx="8" fill="#3a5252"/><rect x="198" y="106" width="89" height="144" rx="24" fill="url(#body)"/><path d="M201 222h83" stroke="#506a6b" stroke-width="3"/><path d="m226 168 16-18 17 18z" fill="#d7e1d7"/><rect width="480" height="340" filter="url(#grain)" opacity=".15" style="mix-blend-mode:multiply"/><path d="M20 23h13m-13 0v13m440 281h-13m13 0v-13" stroke="#34483c" opacity=".35" fill="none"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 480 340">
<defs><linearGradient id="body" x1="0" x2="1" y1="0" y2="1"><stop stop-color="#b39461"/><stop offset="1" stop-color="#243226" stop-opacity=".95"/></linearGradient><filter id="shadow"><feGaussianBlur stdDeviation="10"/></filter><filter id="grain"><feTurbulence type="fractalNoise" baseFrequency=".52" numOctaves="3" stitchTiles="stitch"/></filter></defs>
<rect width="480" height="340" fill="#e0d9c8"/><circle cx="242" cy="164" r="117" fill="#fff" opacity=".14"/><ellipse cx="240" cy="255" rx="108" ry="14" opacity=".25" filter="url(#shadow)" fill="#353830"/><path d="M130 184q10-77 88-79 74 1 91 81z" fill="url(#body)"/><path d="M213 108q-35 25-44 73m52-73q38 21 48 72" stroke="#7e6948" fill="none" stroke-width="2"/><path d="M133 183q53-12 166-3 53 4 62 22-29 22-121 14z" fill="#856c48"/><circle cx="217" cy="105" r="5" fill="#846f4f"/><rect x="201" y="143" width="36" height="20" rx="2" fill="#e7d0a3"/><path d="m209 156 9-8 10 8" fill="none" stroke="#534b36" stroke-width="2"/><rect width="480" height="340" filter="url(#grain)" opacity=".15" style="mix-blend-mode:multiply"/><path d="M20 23h13m-13 0v13m440 281h-13m13 0v-13" stroke="#34483c" opacity=".35" fill="none"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+27
View File
@@ -0,0 +1,27 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="Fieldwork Supply, a fictional outdoor goods storefront and portfolio demo.">
<title>Fieldwork Supply — Equipment for getting out</title>
<link rel="stylesheet" href="style.css"><script defer src="shop.js"></script>
</head>
<body>
<div class="notice">FIELDWORK SUPPLY &nbsp; / &nbsp; A FICTIONAL STOREFRONT</div>
<header class="top"><a class="wordmark" href="#">Fieldwork<span>.</span></a><nav aria-label="Main"><a href="#shop">Shop</a><a href="#story">Our story</a></nav><button id="bag" class="bag" type="button">Bag <span id="count">0</span></button></header>
<main>
<section class="hero">
<div class="hero-copy"><p class="eyebrow"><i></i> THE TRAIL COLLECTION / 01</p><h1>Better<br>outside<span>.</span></h1><p>Thoughtfully designed gear for slow mornings, long trails, and weekends out of range.</p><a class="primary" href="#shop">Explore the collection <span aria-hidden="true">↗</span></a></div>
<div class="hero-art"><img src="landscape.svg" alt="Illustrated mountain range and pine forest at sunset"><span class="image-label">FIG. 01 &nbsp; / &nbsp; ABOVE THE TREELINE</span></div>
</section>
<section id="shop" class="shop"><div class="shop-heading"><div><p class="eyebrow">01 / THE SHOP</p><h2>Trail essentials</h2></div><p>Pack light. Make it count.</p></div>
<div class="controls"><div id="filters" role="group" aria-label="Filter products"></div><label>Sort by <select id="sort"><option value="featured">Featured</option><option value="low">Price: low to high</option><option value="high">Price: high to low</option></select></label></div>
<p class="sr-only" id="results" aria-live="polite"></p><div id="products" class="grid"></div></section>
<section class="story" id="story"><p class="eyebrow">02 / THE FIELD NOTES</p><h2>Good gear leaves room for the good stuff.</h2><p>Fieldwork is an imagined gear shop built around a simple packing list. Six essentials, each with a clear job, for getting out the door and into the day.</p><a href="#shop">Back to the collection ↑</a></section>
</main>
<footer><a class="wordmark" href="#">Fieldwork<span>.</span></a><p>Fictional brand. Portfolio demo.</p><a href="#shop">Shop the collection ↑</a></footer>
<dialog id="product-dialog" aria-labelledby="product-title"><div class="dialog-head"><p class="eyebrow">FIELDWORK / EQUIPMENT</p><form method="dialog"><button class="close" aria-label="Close product details">×</button></form></div><div id="product-detail"></div></dialog>
<dialog id="cart" aria-labelledby="cart-title"><div class="dialog-head"><h2 id="cart-title">Your bag</h2><form method="dialog"><button class="close" aria-label="Close bag">×</button></form></div><div id="cart-items"></div><div id="cart-summary" hidden><p class="subtotal"><strong>Subtotal</strong><strong id="subtotal"></strong></p><button class="primary" id="checkout">Place demo order <span aria-hidden="true">↗</span></button><p class="fine">Demo checkout. No payment or shipping details required.</p></div><p id="order" role="status" hidden></p></dialog>
<div id="toast" role="status" aria-live="polite"></div>
</body>
</html>
+11
View File
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 480 340">
<defs><linearGradient id="body" x1="0" x2="1" y1="0" y2="1"><stop stop-color="#67735b"/><stop offset="1" stop-color="#243226" stop-opacity=".95"/></linearGradient><filter id="shadow"><feGaussianBlur stdDeviation="10"/></filter><filter id="grain"><feTurbulence type="fractalNoise" baseFrequency=".52" numOctaves="3" stitchTiles="stitch"/></filter></defs>
<rect width="480" height="340" fill="#dcd6bb"/><circle cx="242" cy="164" r="117" fill="#fff" opacity=".14"/><ellipse cx="240" cy="255" rx="108" ry="14" opacity=".25" filter="url(#shadow)" fill="#353830"/><path d="M190 119V96q0-41 49-41t49 41v23" stroke="#596450" stroke-width="8" fill="none"/><rect x="181" y="110" width="116" height="122" rx="20" fill="#ebd393"/><rect x="181" y="107" width="116" height="27" rx="10" fill="url(#body)"/><rect x="178" y="207" width="122" height="34" rx="9" fill="url(#body)"/><path d="M197 132v77m84-77v77" stroke="#67735b" stroke-width="8"/><path d="M219 143v54m21-54v54m21-54v54" stroke="#f6e7b9" stroke-width="5"/><circle cx="238" cy="223" r="6" fill="#c9c6a4"/><rect width="480" height="340" filter="url(#grain)" opacity=".15" style="mix-blend-mode:multiply"/><path d="M20 23h13m-13 0v13m440 281h-13m13 0v-13" stroke="#34483c" opacity=".35" fill="none"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 480 340">
<defs><linearGradient id="body" x1="0" x2="1" y1="0" y2="1"><stop stop-color="#a95a42"/><stop offset="1" stop-color="#243226" stop-opacity=".95"/></linearGradient><filter id="shadow"><feGaussianBlur stdDeviation="10"/></filter><filter id="grain"><feTurbulence type="fractalNoise" baseFrequency=".52" numOctaves="3" stitchTiles="stitch"/></filter></defs>
<rect width="480" height="340" fill="#e9d5bd"/><circle cx="242" cy="164" r="117" fill="#fff" opacity=".14"/><ellipse cx="240" cy="255" rx="108" ry="14" opacity=".25" filter="url(#shadow)" fill="#353830"/><path d="M283 137h28q30 0 30 30t-30 31h-32" fill="none" stroke="#7a3e2d" stroke-width="15"/><path d="M164 124h131l-9 91q-2 16-21 16h-69q-20 0-23-16z" fill="url(#body)"/><ellipse cx="229" cy="124" rx="65" ry="16" fill="#422c24"/><ellipse cx="229" cy="123" rx="58" ry="10" fill="#c37c60"/><path d="m213 179 16-20 16 20z" fill="#eed4ad"/><rect width="480" height="340" filter="url(#grain)" opacity=".15" style="mix-blend-mode:multiply"/><path d="M20 23h13m-13 0v13m440 281h-13m13 0v-13" stroke="#34483c" opacity=".35" fill="none"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 480 340">
<defs><linearGradient id="body" x1="0" x2="1" y1="0" y2="1"><stop stop-color="#74855b"/><stop offset="1" stop-color="#243226" stop-opacity=".95"/></linearGradient><filter id="shadow"><feGaussianBlur stdDeviation="10"/></filter><filter id="grain"><feTurbulence type="fractalNoise" baseFrequency=".52" numOctaves="3" stitchTiles="stitch"/></filter></defs>
<rect width="480" height="340" fill="#d5dac9"/><circle cx="242" cy="164" r="117" fill="#fff" opacity=".14"/><ellipse cx="240" cy="255" rx="108" ry="14" opacity=".25" filter="url(#shadow)" fill="#353830"/><path fill="#394738" d="M167 222c-15-84 1-141 34-151l12 17c-22 23-25 55-17 126zm138 0c15-84-1-141-34-151l-12 17c22 23 25 55 17 126z"/><rect x="182" y="86" width="111" height="163" rx="23" fill="url(#body)"/><path d="M182 108V83q0-14 15-14h83q13 0 13 14v25z" fill="#596b4d"/><path d="M187 108h100m-49 1v46" stroke="#27392d" stroke-width="7"/><rect x="229" y="132" width="18" height="16" rx="3" fill="#d5ccab"/><rect x="200" y="161" width="76" height="57" rx="10" fill="#879674"/><path d="M207 176h62" stroke="#445742" stroke-width="3"/><rect width="480" height="340" filter="url(#grain)" opacity=".15" style="mix-blend-mode:multiply"/><path d="M20 23h13m-13 0v13m440 281h-13m13 0v-13" stroke="#34483c" opacity=".35" fill="none"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+100
View File
@@ -0,0 +1,100 @@
"use strict";
const catalog = [
{id:"pack",name:"Ridgeline Pack",category:"Carry",price:86,detail:"A compact daypack with a roll-top closure and an easy-access front pocket."},
{id:"mug",name:"Camp Enamel Mug",category:"Camp",price:24,detail:"An enamel-coated camp mug with a generous handle and a dark rolled rim."},
{id:"cap",name:"Switchback Cap",category:"Wear",price:32,detail:"A soft five-panel cap with an adjustable back for sunny walks and early starts."},
{id:"bottle",name:"Alpine Flask",category:"Carry",price:38,detail:"A narrow insulated flask with a screw-top lid and a carry loop."},
{id:"lantern",name:"Afterglow Lantern",category:"Camp",price:54,detail:"A compact lantern with a warm diffuser and a folding carry handle."},
{id:"blanket",name:"Meadow Blanket",category:"Camp",price:68,detail:"A striped camp blanket that rolls down neatly for a picnic or a cool evening."}
];
const byId = id => document.getElementById(id);
const money = value => new Intl.NumberFormat("en-US",{style:"currency",currency:"USD",maximumFractionDigits:0}).format(value);
const product = id => catalog.find(item => item.id === id);
const art = item => '<img src="' + item.id + '.svg" alt="' + item.name + ' product illustration">';
const storageKey = "fieldwork-bag-v1";
let bag = {};
try {
const stored = JSON.parse(localStorage.getItem(storageKey)) || {};
for (const {id} of catalog) {
if (Number.isSafeInteger(stored[id]) && stored[id] > 0) bag[id] = stored[id];
}
} catch {}
let filter = "All";
let toastTimer;
function renderProducts() {
const items = catalog.filter(item => filter === "All" || item.category === filter);
const order = byId("sort").value;
if (order !== "featured") items.sort((a,b) => order === "low" ? a.price-b.price : b.price-a.price);
byId("products").innerHTML = items.map(item => `<article class="product">
<button class="product-image" data-detail="${item.id}" aria-label="View ${item.name}">${art(item)}</button>
<div class="product-info"><div><p class="eyebrow">${item.category}</p><h3>${item.name}</h3><p class="price">${money(item.price)}</p></div>
<button class="add" data-add="${item.id}" aria-label="Add ${item.name} to bag">+</button></div></article>`).join("");
byId("results").textContent = `${items.length} products shown`;
}
function renderCart() {
const items = catalog.filter(item => bag[item.id]);
byId("count").textContent = items.reduce((sum,item) => sum + bag[item.id],0);
byId("cart-items").innerHTML = items.map(item => `<div class="cart-row">${art(item)}
<div><strong>${item.name}</strong><p class="price">${money(item.price * bag[item.id])}</p></div>
<div class="qty"><button data-adjust="${item.id}" data-step="-1" aria-label="Decrease ${item.name} quantity">−</button>
<span>${bag[item.id]}</span><button data-adjust="${item.id}" data-step="1" aria-label="Increase ${item.name} quantity">+</button></div></div>`).join("") || "<p>Your bag is empty.</p>";
byId("subtotal").textContent = money(items.reduce((sum,item) => sum + item.price * bag[item.id],0));
byId("cart-summary").hidden = !items.length;
}
function updateBag(id, amount) {
bag[id] = (bag[id] || 0) + amount;
if (bag[id] <= 0) delete bag[id];
try {localStorage.setItem(storageKey,JSON.stringify(bag));} catch {}
renderCart();
}
function notify(message) {
byId("toast").textContent = message;
clearTimeout(toastTimer);
toastTimer = setTimeout(() => byId("toast").textContent = "",2400);
}
byId("filters").innerHTML = ["All",...new Set(catalog.map(item => item.category))]
.map(category => `<button data-category="${category}" aria-pressed="${category === filter}">${category}</button>`).join("");
byId("filters").addEventListener("click",event => {
const button = event.target.closest("[data-category]");
if (!button) return;
filter = button.dataset.category;
for (const option of byId("filters").children) option.setAttribute("aria-pressed", option === button);
renderProducts();
});
byId("sort").addEventListener("change",renderProducts);
byId("bag").addEventListener("click",() => {
byId("order").hidden = true;
byId("cart-items").hidden = false;
byId("cart").showModal();
});
document.addEventListener("click",event => {
const button = event.target.closest("button");
if (!button) return;
const {add,detail,adjust,step} = button.dataset;
if (add) {
updateBag(add,1);
if (byId("product-dialog").open) byId("product-dialog").close();
notify(`${product(add).name} added to your bag`);
}
if (detail) {
const item = product(detail);
byId("product-detail").innerHTML = `${art(item)}<h2 id="product-title">${item.name}</h2><p>${item.detail}</p>
<button class="primary" data-add="${item.id}">Add to bag <span>${money(item.price)}</span></button>`;
byId("product-dialog").showModal();
}
if (adjust) {
updateBag(adjust,Number(step));
(byId("cart-items").querySelector(`[data-adjust="${adjust}"][data-step="${step}"]`) || byId("cart").querySelector(".close")).focus();
}
});
byId("checkout").addEventListener("click",() => {
for (const id of Object.keys(bag)) delete bag[id];
try {localStorage.removeItem(storageKey);} catch {}
renderCart();
byId("cart-items").hidden = true;
byId("order").textContent = "Demo order placed. Thanks for exploring Fieldwork.";
byId("order").hidden = false;
byId("cart").querySelector(".close").focus();
});
renderProducts();
renderCart();
+1
View File
File diff suppressed because one or more lines are too long