Work orders: crafting orders claimed by one crafter, open materials requests, escrow, refunds
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
89fd9b3c40
commit
d03f0035eb
7 changed files with 538 additions and 8 deletions
186
static/app.js
186
static/app.js
|
|
@ -12,6 +12,10 @@ let skew = 0; // server clock minus client clock, seconds
|
|||
let page = decodeURIComponent(location.hash.slice(1)) || remember("page") || "town";
|
||||
let selected = null; // bank item
|
||||
let dollPick = null; // equipment slot shown on the character sheet
|
||||
let orderTab = "take"; // work orders: for you, all open, yours
|
||||
let draft = null; // a work order being written
|
||||
let seenOrders = null; // order ids already known, so only new ones toast
|
||||
let deliveredSeen = {}; // how much of each order had arrived
|
||||
let pickedClass = null; // registration
|
||||
const tabs = {}; // skill -> selected recipe tab
|
||||
let refreshing = false;
|
||||
|
|
@ -93,6 +97,7 @@ function accept(state) {
|
|||
S = state;
|
||||
skew = state.now - Date.now() / 1000;
|
||||
if (state.summary) showSummary(state.summary);
|
||||
orderNews(state);
|
||||
const ev = state.upcoming;
|
||||
if (ev?.at_gates && warned !== ev.id) {
|
||||
warned = ev.id;
|
||||
|
|
@ -307,6 +312,7 @@ function sidebar() {
|
|||
const me = S.me, cls = C.classes[me.cls];
|
||||
const doing = me.action && C.actions.find((a) => a.id === me.action.id);
|
||||
const raid = S.upcoming?.kind === "raid";
|
||||
const takeable = (S.orders || []).filter(canTake).length;
|
||||
let html = `
|
||||
<div class="brand"><img src="/static/icon.svg" alt="">Guildhall <small>alpha</small></div>
|
||||
<div class="me">
|
||||
|
|
@ -317,6 +323,7 @@ function sidebar() {
|
|||
<div class="navhead">City</div>
|
||||
${nav("town", "🏘️", "Town square", `${S.city.population}`, "", `Population ${S.city.population}`)}
|
||||
${nav("council", "📜", "Council & threats", "", raid ? `<span class="alert"></span>` : "", raid ? "A threat is coming" : "")}
|
||||
${nav("orders", ORDER_ICON(), "Work orders", "", takeable ? `<span class="badge">${takeable}</span>` : "", takeable ? `${takeable} for you` : "")}
|
||||
${nav("board", "💬", "Town board")}
|
||||
${nav("roster", "👥", "Guild roster", `${S.players.length}`, "", `${S.players.length} ${S.players.length === 1 ? "adventurer" : "adventurers"}`)}
|
||||
<div class="navhead">You</div>
|
||||
|
|
@ -350,7 +357,7 @@ function devbar() {
|
|||
</div></div>`;
|
||||
}
|
||||
|
||||
const TITLES = { town: "Town square", council: "Council & threats", board: "Town board", roster: "Guild roster", bank: "Bank", character: "Character" };
|
||||
const TITLES = { town: "Town square", council: "Council & threats", orders: "Work orders", board: "Town board", roster: "Guild roster", bank: "Bank", character: "Character" };
|
||||
|
||||
function topbar() {
|
||||
let title = TITLES[page] || "Guildhall";
|
||||
|
|
@ -390,7 +397,7 @@ function pageHtml() {
|
|||
}
|
||||
if (page.startsWith("character:")) return characterSheet(page.slice(10));
|
||||
if (page === "character") return characterSheet(S.uid);
|
||||
return ({ town, council, board, roster, bank }[page] || town)();
|
||||
return ({ town, council, orders, board, roster, bank }[page] || town)();
|
||||
}
|
||||
|
||||
function itemChip(id, qty, have) {
|
||||
|
|
@ -429,6 +436,11 @@ function skillPage(id) {
|
|||
html += skillGear(id);
|
||||
if (id === "reconnaissance") html += `<div class="panel small muted">Raises intel on the coming threat. Higher levels open as it nears.</div>`;
|
||||
html += skillHappenings(id);
|
||||
const wanted = (S.orders || []).filter((o) => o.skill === id && canTake(o));
|
||||
if (wanted.length) html += `<div class="banner good happening-banner"><span class="banner-ico">${ORDER_ICON()}</span>
|
||||
<div class="grow"><b>${wanted.length} work order${wanted.length > 1 ? "s" : ""}</b>
|
||||
<div class="small">${wanted.map((o) => esc(item(o.item).name)).join(" · ")}</div></div>
|
||||
<button class="btn" data-nav="orders">Work orders</button></div>`;
|
||||
const live = new Set(S.happenings.problems.map((p) => p.kind));
|
||||
const listed = C.actions.filter((a) => a.skill === id && (a.special !== "problem" || live.has(a.problem)));
|
||||
// Tabs and sections keep their designed order; what's inside each is sorted by the level it needs.
|
||||
|
|
@ -1148,6 +1160,140 @@ function raidPrep(ev) {
|
|||
</div></div>`;
|
||||
}
|
||||
|
||||
// --- work orders ------------------------------------------------------------
|
||||
|
||||
const ORDER_ICON = () => glyph("scroll-unfurled", "#e3c060");
|
||||
|
||||
// An order you could pick up: open, not yours, not claimed by someone else, and your level is high enough.
|
||||
function canTake(o) {
|
||||
if (o.status !== "open" || o.by === S.uid) return false;
|
||||
if (o.kind === "craft" && o.claimed && o.claimed !== S.uid) return false;
|
||||
return S.me.levels[o.skill] >= o.level;
|
||||
}
|
||||
|
||||
// Toasts for new orders you can take, and for things delivered to you. Nothing on the first load.
|
||||
function orderNews(state) {
|
||||
const orders = state.orders || [], uid = state.uid;
|
||||
if (seenOrders) {
|
||||
for (const o of orders) {
|
||||
if (!seenOrders.has(o.id) && o.by !== uid && state.me && state.me.levels[o.skill] >= o.level && o.status === "open") {
|
||||
toast(`📜 ${item(o.item).name}${o.qty > 1 ? ` ×${fmt(o.qty)}` : ""} · ${fmt(o.reward)} gold`, "level");
|
||||
}
|
||||
const was = deliveredSeen[o.id];
|
||||
if (o.by === uid && was !== undefined && o.delivered > was) toast(`📦 ${item(o.item).name} ×${fmt(o.delivered - was)}`, "good");
|
||||
}
|
||||
}
|
||||
seenOrders = new Set(orders.map((o) => o.id));
|
||||
deliveredSeen = Object.fromEntries(orders.map((o) => [o.id, o.delivered]));
|
||||
}
|
||||
|
||||
function orderSlots(o) {
|
||||
const a = C.actions.find((x) => x.id === o.action), out = a.outputs[o.item];
|
||||
const left = Math.ceil((o.qty - o.delivered) / out), all = Math.ceil(o.qty / out);
|
||||
return Object.entries(a.inputs).map(([id, per]) => {
|
||||
const name = item(id).name;
|
||||
if (id in o.provided) {
|
||||
return `<div class="ro-slot filled" title="${esc(`${name}: provided`)}"><span class="doll-slot">${item(id).icon}
|
||||
<span class="swap-qty">${fmt(per * all)}</span><span class="tick">✓</span></span></div>`;
|
||||
}
|
||||
const need = per * (o.status === "open" ? left : all), have = S.me.bank[id] || 0;
|
||||
return `<div class="ro-slot open" title="${esc(`${name}: crafter brings`)}"><span class="doll-slot empty-req">${item(id).icon}</span>
|
||||
<span class="ro-have ${o.by === S.uid ? "muted" : have < need ? "bad" : "good"}">${o.by === S.uid ? `×${fmt(need)}` : `${fmt(have)} / ${fmt(need)}`}</span></div>`;
|
||||
}).join("");
|
||||
}
|
||||
|
||||
function orderCard(o) {
|
||||
const it = item(o.item), mine = o.by === S.uid, skill = skillOf(o.skill);
|
||||
const short = S.me.levels[o.skill] < o.level, have = S.me.bank[o.item] || 0, left = o.qty - o.delivered;
|
||||
const progress = o.qty > 1 || o.kind === "gather"
|
||||
? `<div class="small muted">${fmt(o.delivered)} / ${fmt(o.qty)}${o.kind === "gather" && Object.keys(o.from).length
|
||||
? ` · ${Object.entries(o.from).map(([uid, n]) => `${esc(playerName(uid))} ${fmt(n)}`).join(", ")}` : ""}</div>
|
||||
<div class="bar"><span style="width:${o.delivered / o.qty * 100}%"></span></div>` : "";
|
||||
let buttons = "", status = "";
|
||||
if (o.status !== "open") {
|
||||
status = `<span class="tag ${o.status === "done" ? "good" : ""}">${{ done: "Done", cancelled: "Cancelled", expired: "Expired" }[o.status]}</span>`;
|
||||
} else if (mine) {
|
||||
buttons = `<button class="btn danger" data-order-cancel="${o.id}">Cancel</button>`;
|
||||
if (o.claimed) status = `<span class="tag">${esc(playerName(o.claimed))}</span>`;
|
||||
} else if (o.kind === "gather") {
|
||||
buttons = `<button class="btn primary" data-order-deliver="${o.id}" ${have ? "" : "disabled"}>Deliver ${fmt(Math.min(have, left))}</button>`;
|
||||
} else if (o.claimed && o.claimed !== S.uid) {
|
||||
status = `<span class="tag">${esc(playerName(o.claimed))}</span>`;
|
||||
} else {
|
||||
const working = S.me.action?.order === o.id;
|
||||
const why = short ? `Needs ${skill.name.toLowerCase()} ${o.level}` : S.me.blocked[o.action] && !working
|
||||
&& !S.me.blocked[o.action].startsWith("Out of") ? S.me.blocked[o.action] : "";
|
||||
buttons = `${o.claimed === S.uid ? `<button class="btn" data-order-drop="${o.id}">Drop</button>` : ""}
|
||||
${have ? `<button class="btn" data-order-deliver="${o.id}">Deliver ${fmt(Math.min(have, left))}</button>` : ""}
|
||||
${working ? `<span class="tag gold">Crafting</span>` : `<button class="btn primary" data-order-work="${o.id}" ${why ? `disabled title="${esc(why)}"` : ""}>Craft it</button>`}`;
|
||||
}
|
||||
const reward = o.kind === "gather" ? `${fmt(Math.round(o.reward / o.qty))} each` : fmt(o.reward);
|
||||
return `<div class="action order">
|
||||
<div class="top"><div class="ico">${it.icon}</div><div>
|
||||
<div class="name">${o.kind === "gather" ? "Wanted: " : ""}${esc(it.name)}${o.qty > 1 ? ` ×${fmt(o.qty)}` : ""}</div>
|
||||
<div class="sub">${mine ? "Yours" : esc(playerName(o.by))} · ${ago(o.at)}${o.held ? ` · ${fmt(o.held)} waiting for bank space` : ""}</div></div>
|
||||
<span class="ro-req ${short && !mine ? "bad" : ""}" title="${esc(`${skill.name} ${o.level}`)}">${skill.icon} ${o.level}</span></div>
|
||||
${o.kind === "craft" ? `<div class="ro-slots">${orderSlots(o)}</div>` : ""}
|
||||
${progress}
|
||||
<div class="foot"><span class="item-chip ro-reward">${COIN} ${reward}</span>${status}<span class="btns">${buttons}</span></div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function orderComposer() {
|
||||
const d = draft, list = d.kind === "craft" ? C.order_recipes : C.order_raw;
|
||||
const bySkill = {};
|
||||
for (const [id, action] of Object.entries(list)) {
|
||||
const a = C.actions.find((x) => x.id === action);
|
||||
(bySkill[a.skill] ||= []).push([id, a.level]);
|
||||
}
|
||||
const options = Object.entries(bySkill).map(([skill, items]) => `<optgroup label="${esc(skillOf(skill).name)}">${items
|
||||
.sort((x, y) => x[1] - y[1]).map(([id, level]) => `<option value="${id}" ${d.item === id ? "selected" : ""}>${esc(item(id).name)} (${level})</option>`).join("")}</optgroup>`).join("");
|
||||
let slots = "", maker = "";
|
||||
const a = d.item && C.actions.find((x) => x.id === list[d.item]);
|
||||
if (a) {
|
||||
const smiths = S.players.filter((p) => p.id !== S.uid && p.levels[a.skill] >= a.level).length;
|
||||
maker = `<div class="small muted" title="${esc(`${skillOf(a.skill).name} ${a.level} · ${smiths} can make it`)}">${skillOf(a.skill).icon} ${a.level} · 👥 ${smiths}</div>`;
|
||||
if (d.kind === "craft") {
|
||||
const runs = Math.ceil(d.qty / a.outputs[d.item]);
|
||||
slots = Object.entries(a.inputs).map(([id, per]) => {
|
||||
const need = per * runs, have = S.me.bank[id] || 0, on = d.provide.includes(id);
|
||||
return `<div class="ro-slot ${on ? "filled" : "open"}"><button class="doll-slot ${on ? "on" : "empty-req"}" data-provide="${id}"
|
||||
${!on && have < need ? `disabled title="${esc(`${item(id).name}: ${fmt(have)} / ${fmt(need)}`)}"` : `title="${esc(item(id).name)}"`}>
|
||||
${item(id).icon}<span class="swap-qty">${fmt(need)}</span>${on ? `<span class="tick">✓</span>` : ""}</button>
|
||||
<span class="ro-have ${have < need ? "bad" : "muted"}">${fmt(have)}</span></div>`;
|
||||
}).join("");
|
||||
}
|
||||
}
|
||||
const gold = d.kind === "craft" ? d.reward : d.reward * d.qty;
|
||||
return `<div class="panel compose">
|
||||
<div class="tabs" style="margin:0 0 14px"><button class="tab ${d.kind === "craft" ? "on" : ""}" data-draft-kind="craft">Craft</button>
|
||||
<button class="tab ${d.kind === "gather" ? "on" : ""}" data-draft-kind="gather">Materials</button></div>
|
||||
<div class="compose-grid">
|
||||
<div><select data-draft="item"><option value="">Item…</option>${options}</select>
|
||||
${a ? `<div class="ro-pick"><span class="doll-slot on">${item(d.item).icon}</span><div><b>${esc(item(d.item).name)}</b>${maker}</div></div>` : ""}
|
||||
<div class="stepper">×<input type="number" min="1" value="${d.qty}" data-draft="qty"></div></div>
|
||||
<div>${slots ? `<div class="ro-slots big">${slots}</div>` : ""}</div>
|
||||
<div><div class="reward-input">${COIN}<input type="number" min="0" value="${d.reward}" data-draft="reward">
|
||||
${d.kind === "gather" ? `<span class="muted small">each · ${fmt(gold)}</span>` : ""}</div>
|
||||
<button class="btn primary" data-order-post ${a && gold <= S.me.gold ? "" : "disabled"}>Post</button></div>
|
||||
</div></div>`;
|
||||
}
|
||||
|
||||
function orders() {
|
||||
const all = S.orders || [], open = all.filter((o) => o.status === "open");
|
||||
const takeable = open.filter(canTake), yours = all.filter((o) => o.by === S.uid);
|
||||
const lists = { take: takeable, open, yours };
|
||||
if (!lists[orderTab]) orderTab = "take";
|
||||
const shown = lists[orderTab];
|
||||
const tab = (key, label, n) => `<button class="tab ${orderTab === key ? "on" : ""}" data-otab="${key}">${label}${n ? ` <b class="badge">${n}</b>` : ""}</button>`;
|
||||
return `<div class="panel"><div class="skill-head"><div class="big">${ORDER_ICON()}</div>
|
||||
<div class="info"><h2>Work orders</h2></div>
|
||||
<button class="btn ${draft ? "" : "primary"}" data-order-new>${draft ? "Close" : "New order"}</button></div></div>
|
||||
${draft ? orderComposer() : ""}
|
||||
<div class="tabs">${tab("take", "For you", takeable.length)}${tab("open", "All open", 0)}${tab("yours", "Yours", 0)}</div>
|
||||
${shown.length ? `<div class="grid wide">${shown.map(orderCard).join("")}</div>` : `<p class="muted">None.</p>`}`;
|
||||
}
|
||||
|
||||
// --- board & roster -------------------------------------------------------
|
||||
|
||||
function board() {
|
||||
|
|
@ -1446,6 +1592,24 @@ document.addEventListener("click", (e) => {
|
|||
if (d.start) return act("/api/action", { id: d.start });
|
||||
if (d.once) return act("/api/action", { id: d.once, once: true });
|
||||
if ("stop" in d) return act("/api/stop", {});
|
||||
if ("orderNew" in d) {
|
||||
draft = draft ? null : { kind: "craft", item: "", qty: 1, provide: [], reward: 0 };
|
||||
return render();
|
||||
}
|
||||
if (d.draftKind) { draft = { kind: d.draftKind, item: "", qty: 1, provide: [], reward: 0 }; return render(); }
|
||||
if (d.provide) {
|
||||
draft.provide = draft.provide.includes(d.provide) ? draft.provide.filter((x) => x !== d.provide) : [...draft.provide, d.provide];
|
||||
return render();
|
||||
}
|
||||
if ("orderPost" in d) {
|
||||
const body = { kind: draft.kind, item: draft.item, qty: draft.qty, provide: draft.provide, reward: draft.reward };
|
||||
return api("/api/order", body).then((state) => { draft = null; orderTab = "yours"; accept(state); }).catch((err) => toast(err.message, "bad"));
|
||||
}
|
||||
if (d.otab) { orderTab = d.otab; return render(); }
|
||||
if (d.orderWork) return act("/api/order/work", { id: Number(d.orderWork) });
|
||||
if (d.orderDrop) return act("/api/order/drop", { id: Number(d.orderDrop) });
|
||||
if (d.orderDeliver) return act("/api/order/deliver", { id: Number(d.orderDeliver) });
|
||||
if (d.orderCancel) return act("/api/order/cancel", { id: Number(d.orderCancel) });
|
||||
if (d.doll) {
|
||||
dollPick = dollPick === d.doll ? null : d.doll;
|
||||
return render();
|
||||
|
|
@ -1492,6 +1656,24 @@ document.addEventListener("click", (e) => {
|
|||
});
|
||||
|
||||
document.addEventListener("change", (e) => {
|
||||
const key = e.target.dataset.draft;
|
||||
if (!key || !draft) return;
|
||||
if (key === "item") {
|
||||
const list = draft.kind === "craft" ? C.order_recipes : C.order_raw;
|
||||
draft.item = e.target.value;
|
||||
draft.provide = [];
|
||||
// A starting price: what the item's worth, each.
|
||||
draft.reward = draft.item ? item(draft.item).value * (draft.kind === "craft" ? draft.qty : 1) : 0;
|
||||
if (!list[draft.item]) draft.item = "";
|
||||
} else {
|
||||
draft[key] = Math.max(key === "qty" ? 1 : 0, Math.floor(Number(e.target.value) || 0));
|
||||
if (key === "qty" && draft.kind === "craft" && draft.item) {
|
||||
// Drop provided reagents the bank can no longer cover at the new quantity.
|
||||
const a = C.actions.find((x) => x.id === C.order_recipes[draft.item]), runs = Math.ceil(draft.qty / a.outputs[draft.item]);
|
||||
draft.provide = draft.provide.filter((id) => (S.me.bank[id] || 0) >= a.inputs[id] * runs);
|
||||
}
|
||||
}
|
||||
render();
|
||||
});
|
||||
|
||||
$("#modal").addEventListener("click", (e) => { if (e.target.id === "modal") e.target.hidden = true; });
|
||||
|
|
|
|||
|
|
@ -130,6 +130,7 @@
|
|||
<symbol id="scale-mail" viewBox="0 0 512 512"><path fill="currentColor" d="M174.688 61.094L144.155 78.72c60.143 71.885 171.856 69.547 222.78 0l-30.53-17.626c-11.46 23.49-43.57 40.625-81.156 40.625s-69.1-17.137-80.563-40.626zm208.5 27c-5.07 7.136-10.657 13.675-16.72 19.594l18.97 34.343l22.03-39.905l-24.28-14.03zm-255.438.094L53.937 130.81l66.876 112.375l33.874-19.656l-2.78 62.595l12.53 22.656l20.75-37.56l-15.375-27.814l16.344-9.03l22.78 41.218l22.75-41.22l16.345 9.032l-30.936 56l-8.156 14.813l-8.188-14.814l-4.906-8.875l-23.25 42.064l-8.188 14.812l-8.156-14.812l-5.938-10.72l-3.5 78.938h81.97l-28.532-51.687l16.375-9.03l22.75 41.217l22.75-41.218l.53.28l-13.874-25.093l16.345-9.03l22.75 41.22l22.78-41.22L312 307l-14.72-26.656l16.376-9.03l22.75 41.186l22.72-41.125L357 223.53l33 19.157l66.72-112.156l-33.033-19.06l-30.062 54.405l-8.188 14.813l-8.187-14.813l-6.438-11.656l-25.718 46.593l-8.188 14.812l-8.156-14.813l-6.75-12.218l-25.438 46.062l-8.156 14.813l-8.187-14.814l-30.94-56l16.376-9.03l22.75 41.186l22.75-41.187l.563.313l-13.564-24.563c-29.667 7.446-61.705 6.438-91.687-3.188l-25.814 46.75l-8.187 14.813l-8.19-14.813l-5.624-10.218l-20.25 36.624l-8.187 14.812l-8.157-14.812l-30.938-56l16.344-9.03l22.78 41.217l17.72-32.124l-14.595-26.47l16.344-9.03l19.56 35.406l.064.032l-.03.03l3.155 5.72l16.374-29.656c-22.918-10.325-44.015-26.047-61.094-47.25zm224.22 31.874c-10.98 8.186-22.995 14.723-35.658 19.594l20.625 37.313l22.75-41.22l1.344.75l-9.06-16.438zm120.936 19.813l-66.75 112.188l22.906 13.28L493.875 152l-20.97-12.125zm-435.156.28L17.22 152l65.405 113.344l22-12.78l-66.875-112.41zm322.97 167.032l-16.126 29.188l-8.188 14.813l-8.187-14.813l-6.44-11.656l-25.717 46.56l-8.188 14.814l-8.156-14.813l-6.75-12.217l-23.033 41.75h114.938l-4.156-93.625zM146 419.5l-1.22 27.25h222.126l-1.22-27.25h-126.06l-.25.47l-.25-.47z"/></symbol>
|
||||
<symbol id="screaming" viewBox="0 0 512 512"><path fill="currentColor" d="M16.406 17.53v14.595l140.75 132.563L105.75 17.53zm123.22 0l68.343 124.407l-2.907-124.406h-65.438zm100.468 0l22.78 118.564L298.5 17.53zm93.437 0L317.19 147.75l90.53-130.22h-74.187zm106.283 0l-74.157 158.282L495.876 76.5V17.53h-56.063zM16.406 71.25v84.094l99.28 46.72zm479.47 46.156l-93.438 99.28l93.437-32.717v-66.564zm-312.188 59.47c-10.92.086-25.162 10.363-36.125 27.374c-6.48 10.054-10.708 20.766-12.5 30.406c-49.04 22.678-113.907 63.906-113.907 63.906l148.438 126l15 67.782h144.28l11.313-64.656l.094.093l152.25-129.217s-66.033-41.99-115.217-64.532c-1.858-9.475-6.037-19.945-12.375-29.78c-10.964-17.01-25.237-27.288-36.157-27.375c-3.64-.03-6.906 1.073-9.56 3.438c-2.42 2.154-4.117 5.17-5.126 8.812c1.253 1.576 2.484 3.176 3.656 4.844c11.65 16.57 19.578 37.626 22.47 61c8.607 7.78 17.563 11.763 24.717 10.78l54.594 35.938L314.94 359.5c-4.07 5.263-8.572 10.022-13.438 14.156c-5.576-4.344-11.033-7.95-16.438-10.812c6.327-4.49 12.208-10.48 17.407-17.875c12.3-17.496 20.28-42.414 20.28-70.126s-7.98-52.63-20.28-70.125c-12.3-17.496-28.3-27.314-45.44-27.314c-17.138 0-33.136 9.818-45.436 27.313c-12.3 17.493-20.28 42.412-20.28 70.124s7.98 52.63 20.28 70.125c4.54 6.456 9.58 11.866 15 16.124c-5.51 2.618-11.07 6.023-16.688 10.22a96 96 0 0 1-10.5-11.44l-105.25-58.187l54.438-35.843c7.49.513 16.76-4.09 25.5-12.594c3.06-22.683 10.856-43.12 22.22-59.28a98 98 0 0 1 2.436-3.314c-.934-4.326-2.752-7.896-5.5-10.344c-2.655-2.364-5.923-3.466-9.563-3.437zm61.093 58.468a73.4 73.4 0 0 0-1.03 12.312c0 2.244.067 4.41.25 6.53c-11.27.942-22.108 3.384-32.063 7.345l-6.906-17.342c12.538-4.99 25.953-7.912 39.75-8.844zm28.22.78c11.567 1.376 23.15 4.035 34.47 7.907l-6.033 17.657a148.5 148.5 0 0 0-27.906-6.625a75 75 0 0 0 .345-7.406c0-4.045-.303-7.93-.875-11.53zm-16.5 41.314c20.266 0 36.72 16.404 36.72 36.656c0 20.255-16.454 36.687-36.72 36.687s-36.688-16.434-36.688-36.686s16.422-36.656 36.688-36.656z"/></symbol>
|
||||
<symbol id="screw" viewBox="0 0 512 512"><path fill="currentColor" d="M259.2 20.25c-24.2.16-49.4 5.94-73 16.17c-26.5 40.57-38.4 73.68-30.9 118.98c-26.9-15.8-43.1-31.6-48.5-58.83c-8.74 11.03-16 22.83-21.41 35.13c-14.06 32-15.85 67.5 1.15 104.1L368.2 77.3c-22.6-34.19-53.9-51.23-88.5-55.81c-4.7-.62-9.4-1-14.2-1.16c-2.1-.1-4.2-.1-6.3-.1zm67.6 101.85l-40.9 23l-61.5 93.9l-41.3 60.6l15.4 10.6l22-32.3c8.3 12.2 16.8 24.2 25.7 36.1l86.7-120.9q-11.85-22.05-24.9-43.5zm-76.1 42.8l-74.4 41.8c7.1 12.6 14.6 25.1 22.4 37.6zm108.2 22.4L239.3 355.5l15.2 10.8l15.4-21.6c11 13.6 22.4 26.9 34.1 39.9l69.4-103.7c-6.7-17.2-14.1-34.2-22-50.9l22.7-31.9zm37.3 93.7l-14.6 21.6s-.1-.1-.1-.2l-24.9 37.3l-61.3 90.8l15.4 10.4l19.4-28.7c8 8.2 16.2 16.2 24.6 23.9l47.5-66.9c-3.9-14.9-8.3-29.8-13.1-44.5l22.5-33.3zm26.3 93.1L408.2 394c-.1-.3-.1-.5-.2-.8l-39.4 55.5l.1.1l-13.5 18.8l15.2 11l12.6-17.5c12.9 10.8 26.2 21 39.6 30.7c-1.8-24.3-4.9-48.6-9.3-72.7l24.4-34z"/></symbol>
|
||||
<symbol id="scroll-unfurled" viewBox="0 0 512 512"><path fill="currentColor" d="M103.432 17.844a87 87 0 0 0-3.348.08q-3.822.163-7.604.678c-20.167 2.747-39.158 13.667-52.324 33.67c-24.613 37.4 2.194 98.025 56.625 98.025c.536 0 1.058-.012 1.583-.022v.704h60.565c-10.758 31.994-30.298 66.596-52.448 101.43a283 283 0 0 0-6.29 10.406l34.878 35.733l-56.263 9.423c-32.728 85.966-27.42 182.074 48.277 182.074v-.002l9.31.066c23.83-.57 46.732-4.298 61.325-12.887c4.174-2.458 7.63-5.237 10.467-8.42h-32.446c-20.33 5.95-40.8-6.94-47.396-25.922c-8.956-25.77 7.52-52.36 31.867-60.452a55.6 55.6 0 0 1 17.565-2.834v-.406h178.33c-.57-44.403 16.35-90.125 49.184-126c23.955-26.176 42.03-60.624 51.3-94.846l-41.225-24.932l38.272-6.906l-43.37-25.807h-.005l.002-.002l.002.002l52.127-8.85c-5.232-39.134-28.84-68.113-77.37-68.113C341.14 32.26 222.11 35.29 149.34 28.496c-14.888-6.763-30.547-10.723-45.908-10.652m.464 18.703c13.137.043 27.407 3.804 41.247 10.63l.033-.07c4.667 4.735 8.542 9.737 11.68 14.985H82.92l10.574 14.78c10.608 14.83 19.803 31.99 21.09 42.024c.643 5.017-.11 7.167-1.814 8.836c-1.705 1.67-6.228 3.875-15.99 3.875c-40.587 0-56.878-44.952-41.012-69.06C66.238 46.64 79.582 39.22 95.002 37.12a64 64 0 0 1 8.894-.573M118.5 80.78h46.28c4.275 15.734 3.656 33.07-.544 51.51H131.52c1.9-5.027 2.268-10.574 1.6-15.77c-1.527-11.913-7.405-24.065-14.62-35.74m101.553 317.095c6.44 6.84 11.192 15.31 13.37 24.914c3.797 16.736 3.092 31.208-1.767 43.204c-4.526 11.175-12.576 19.79-22.29 26h237.19c14.448 0 24.887-5.678 32.2-14.318c7.312-8.64 11.2-20.514 10.705-32.352a47.7 47.7 0 0 0-2.407-13.18l-69.91-8.205l42.017-20.528c-8.32-3.442-18.64-5.537-31.375-5.537H220.053zm-42.668.506a37 37 0 0 0-3.457.153a34.8 34.8 0 0 0-7.824 1.63c-15.11 5.02-25.338 21.54-20.11 36.583c3.673 10.57 15.347 17.71 25.654 13.938l1.555-.57h43.354c.946-6.36.754-13.882-1.358-23.192c-3.71-16.358-20.543-28.483-37.815-28.54z"/></symbol>
|
||||
<symbol id="sewing-machine" viewBox="0 0 512 512"><path fill="currentColor" d="m61.67 119.8l-12.69 68.3c-5.47 29.6-1.02 60.1 12.69 86.8h51.03V119.8zm68.63 0v115.6h2.9c6.2 0 11.2-5.1 11.2-11.3V119.8zm31.8 0v104.3c0 15.9-12.9 28.8-28.9 28.8h-2.9v81.7h14.9v-59.7h19.6s7.6-15.3 13.6-27.7c4.7-9.2 14.1-15.1 24.5-15.1h80.2c15 0 27.2 12.1 27.2 27.1v91.5H25.95l10.57 68.5H455.1l10.5-63.1l-21.1-236.3zm310.1 23.4c-4.9.4-8.5 4.7-8 9.5l4.3 47.4c.4 4.8 4.7 8.4 9.5 8c4.9-.4 8.4-4.8 8-9.6l-4.2-47.3c-.4-4.5-4.2-8-8.7-8zm-86.8.1c21.7 0 39.2 17.5 39.2 39.2s-17.5 39.2-39.2 39.2s-39.2-17.5-39.2-39.2s17.5-39.2 39.2-39.2m0 17.5c-11.9 0-21.7 9.8-21.7 21.7c0 12 9.8 21.7 21.7 21.7s21.7-9.7 21.7-21.7c0-11.9-9.8-21.7-21.7-21.7m0 89.9c26.6 0 48.2 21.7 48.2 48.2c0 26.6-21.6 48.2-48.2 48.2s-48.2-21.6-48.2-48.2c0-26.5 21.6-48.2 48.2-48.2m0 17.6c-16.9 0-30.6 13.7-30.6 30.6s13.7 30.5 30.6 30.5s30.5-13.6 30.5-30.5s-13.6-30.6-30.5-30.6M92.71 287.8v17.6h20.09v-17.6zM36.52 438.9v26.5H454.9v-26.5z"/></symbol>
|
||||
<symbol id="sewing-needle" viewBox="0 0 512 512"><path fill="currentColor" d="M56.813 18.438c-.47-.005-.938.014-1.407.03q-.939.035-1.875.126c-7.468.72-14.682 4.148-22.31 11.125c-9.985 9.128-12.863 17.66-11.407 27.718c1.455 10.056 8.353 22.236 21.125 34.906c2.895 2.872 6.126 5.737 9.593 8.625c1.27 1.055 2.565 2.13 3.907 3.186a201 201 0 0 0 3.438 2.656c.232.175.454.358.688.532C74.71 119.38 95.558 131.28 120.25 142.22c159.723 161.31 288.815 297.553 374.594 352.124c-51.11-85.624-187.432-214.62-350-375.531c-10.23-23.802-21.115-43.71-32.094-59.282c35.46-10.19 82.517-2.294 125.75 40.94c40.243 40.242 52.642 90.776 41.375 128.343c4.89 4.914 9.883 9.913 14.688 14.75c19.07-46.1 4.573-108.895-42.844-156.313c-35.694-35.693-75.48-50.324-111.032-50.22c-1.147.005-2.3.03-3.438.064c-21.844.65-41.957 6.898-58.344 17.187a102 102 0 0 0-8.312 5.814c1.33 3.094 2.758 5.884 4.406 8.218c1.92 2.72 4.004 4.936 6.906 6.688c2.202-1.717 4.498-3.375 6.938-4.906a89 89 0 0 1 3.25-1.97c8.196 10.088 15.465 24.624 25.47 47.25c-23.907-10.568-38.822-18.04-49.033-27c.024-.03.04-.063.064-.093c-.433-.33-.867-.655-1.28-1q-.018-.013-.033-.03c-11.215-10.375-16.532-22.934-22.31-44.5c5.988 1.605 11.256 3.19 16 4.844a137 137 0 0 1 26.81-15.875c-9.5-7.738-18.552-11.93-26.686-12.97c-1.443-.185-2.87-.3-4.282-.313zM493.28 64.374c-77.985 0-125.587 28.886-146.124 69.406s-13.994 90.39 9.688 131.407c11.385 19.72 17.807 40.687 19.187 61.22a4330 4330 0 0 1 18.47 19.187c2.31-29.955-5.072-61.35-21.47-89.75c-21.13-36.602-26.155-80.112-9.186-113.594s55.94-59.188 129.437-59.188V64.376zM37.907 114.938c-2.894 23.925 6.18 50.21 29.532 73.562l13.218-13.188c-15.97-15.97-23.188-31.833-24.437-46.593c-6.646-4.51-12.754-9.102-18.314-13.782zM124.062 277c-24.466-.028-45.04 4.92-61.343 13.656c-24.842 13.31-39.363 35.865-40.75 60.47c-2.78 49.207 44.38 102.65 140.405 118.28c101.63 16.544 170.303-9.184 205.406-54.062q1.513-1.933 2.94-3.906c-4.552-4.232-9.148-8.52-13.783-12.875c-1.233 1.79-2.52 3.55-3.875 5.28c-29.97 38.315-90.355 62.97-187.687 47.126c-90.394-14.715-126.796-62.527-124.75-98.783c1.023-18.127 10.982-34.37 30.938-45.062c19.955-10.692 50.267-15.364 90.5-7.47c34.077 6.69 62.938 3.47 85.437-6.936c-4.72-4.65-9.49-9.33-14.28-14.064c-17.545 6.452-39.89 8.088-67.564 2.656c-14.895-2.923-28.778-4.297-41.594-4.312z"/></symbol>
|
||||
<symbol id="sewing-string" viewBox="0 0 512 512"><path fill="currentColor" d="M153.9 51.5c-34.6 0-65.87 4.45-87.79 11.3c-10.96 3.43-19.55 7.51-24.76 11.37c-5.22 3.85-6.41 6.7-6.41 8.33s1.19 4.48 6.41 8.33c5.21 3.86 13.8 7.94 24.76 11.37c21.92 6.8 53.19 11.3 87.79 11.3s65.9-4.5 87.8-11.3c11-3.43 19.6-7.51 24.8-11.37c5.2-3.85 6.4-6.7 6.4-8.33s-1.2-4.48-6.4-8.33c-5.2-3.86-13.8-7.94-24.8-11.37c-21.9-6.85-53.2-11.3-87.8-11.3M388 70.48c-2.5 0-5 0-7.5.16c-20.2 1.15-42.3 7.56-63.1 15.43c-12.5 4.72-24.5 9.98-35.3 15.03c-1.5 1.5-3.2 2.9-4.9 4.2q-7.65 5.55-18.3 9.9v17.9c13.5-7.4 38.1-20.1 64.8-30.2c19.9-7.5 40.8-13.32 57.8-14.29c17-.98 28.9 2.5 35.4 11.69c6 8.5 5 14.1-.7 23.4c-5.6 9.3-16.8 20.2-29.5 31.6c-12.6 11.3-26.5 23.2-37.4 36.4s-19.2 28.2-18.2 45.2c1.5 24.8 21.4 48.3 39.9 67.4c18.4 19 36.5 32.9 36.5 32.9l11-14.2s-17.2-13.3-34.6-31.2c-17.4-18-33.9-41.4-34.8-56v-.1c-.7-10.3 4.6-21.1 14.1-32.5c9.5-11.5 22.8-23 35.6-34.6c12.8-11.5 25.1-22.8 32.9-35.6c7.7-12.8 9.7-29.2-.2-43.11c-9.8-13.91-26.1-19.1-43.5-19.41M153.9 74.5a48 8 0 0 1 48 8a48 8 0 0 1-48 8a48 8 0 0 1-48-8a48 8 0 0 1 48-8m-87 46.7v10.6c58.3 14 116 13 174 6.9v-17.5c-23.6 6.5-53.9 10.3-87 10.3c-33.2 0-63.38-3.8-87-10.3m0 29.1v24.4c62.9 2.9 119.7-3.4 174-13.9v-4.1c-57.1 6-115 7-174-6.4m174 28.7c-54 10.3-111.1 16.5-174 13.8v18.7c64 1.5 123.2-2.5 174-15.9zm0 35.3c-52 12.9-111.1 16.6-174 15.2v3.2c61.5 8.4 120.8 13.8 174 10.8zm-174 36.5v8.9c53.3 17 112.9 17.8 174 16.9v-15.1c-54.1 2.9-113.2-2.3-174-10.7m0 27.7v31.8c52.7 3.4 102.8-3.5 152.2-15.5c-52 .4-103.9-1.7-152.2-16.3m174 29.4c-55.7 14.7-113.1 24.3-174 20.5v16c57.6.2 114.8-1 174-14.2zm221.7 19a20.5 40.99 51.48 0 0-32.8 14.1a20.5 40.99 51.48 0 0-.2.1l-.6.6a20.5 40.99 51.48 0 0-8.9 8.5L265.9 497.5l176.3-115.9a20.5 40.99 51.48 0 0 13.1-8.5a20.5 40.99 51.48 0 0 19.3-41.5a20.5 40.99 51.48 0 0-12-4.7M452.5 342a10.25 20.5 51.48 0 1 .1 0a10.25 20.5 51.48 0 1 6 2.3a10.25 20.5 51.48 0 1-9.7 20.8a10.25 20.5 51.48 0 1-22.4 4.8a10.25 20.5 51.48 0 1 9.6-20.8a10.25 20.5 51.48 0 1 16.4-7.1m-211.6 6.6c-59.9 12.8-117.3 14-173.99 13.8c0 0 .41.9 3.71 2.8c3.91 2.2 10.75 4.6 19.24 6.5c16.94 3.8 40.54 5.8 64.04 5.8s47.1-2 64-5.8c8.5-1.9 15.4-4.3 19.3-6.5c3.4-2 3.7-3 3.7-2.8zM49.17 365.5c-3.1 1.6-5.74 3.1-7.82 4.7c-5.22 3.8-6.41 6.7-6.41 8.3s1.19 4.5 6.41 8.3c5.21 3.9 13.8 8 24.76 11.4c21.92 6.8 53.19 11.3 87.79 11.3s65.9-4.5 87.8-11.3c11-3.4 19.6-7.5 24.8-11.4c5.2-3.8 6.4-6.7 6.4-8.3s-1.2-4.5-6.4-8.3c-2.1-1.6-4.7-3.2-7.9-4.7c-1.2 6.9-6.7 12-12.5 15.3c-6.6 3.8-14.7 6.4-24.3 8.5c-19 4.2-43.4 6.2-67.9 6.2s-48.9-2-67.95-6.2c-9.51-2.1-17.68-4.7-24.26-8.5c-5.79-3.2-11.27-8.4-12.52-15.3"/></symbol>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 286 KiB After Width: | Height: | Size: 288 KiB |
|
|
@ -571,3 +571,34 @@ button.sheet-skill:hover { background: var(--panel-2); }
|
|||
.happening-banner { flex-wrap: wrap; }
|
||||
.happening-banner .grow { min-width: min(260px, 70%); }
|
||||
.happening-banner > .btn { margin-left: auto; }
|
||||
|
||||
/* Work orders */
|
||||
.badge { background: var(--gold); color: var(--bg); border-radius: 99px; font-size: 11px; font-weight: 800; padding: 1px 7px; }
|
||||
.order .top { align-items: flex-start; }
|
||||
.ro-req { margin-left: auto; display: inline-flex; align-items: center; gap: 4px; font-weight: 700; font-size: 13px; }
|
||||
.ro-req.bad { color: var(--bad); }
|
||||
.ro-req .gi { width: 16px; height: 16px; }
|
||||
.ro-slots { display: flex; gap: 10px; flex-wrap: wrap; margin: 4px 0; }
|
||||
.ro-slot { display: grid; justify-items: center; gap: 3px; font-size: 11px; }
|
||||
.ro-slot .doll-slot { width: 46px; height: 46px; }
|
||||
.ro-slot .doll-slot .gi { width: 30px; height: 30px; }
|
||||
.ro-slot.filled .doll-slot { border-color: rgba(95, 174, 90, .7); background: rgba(95, 174, 90, .1); }
|
||||
.doll-slot.empty-req { border-style: dashed; background: transparent; }
|
||||
.doll-slot.empty-req .gi { opacity: .45; }
|
||||
.tick { position: absolute; bottom: -5px; right: -5px; background: var(--good); color: var(--bg); border-radius: 50%; width: 15px; height: 15px;
|
||||
font-size: 10px; display: grid; place-items: center; font-weight: 900; }
|
||||
.ro-have { font-weight: 700; }
|
||||
.ro-have.good { color: var(--good); } .ro-have.bad { color: var(--bad); }
|
||||
.ro-reward { font-weight: 700; }
|
||||
.order .foot { gap: 8px; }
|
||||
.ro-slots.big .doll-slot { width: 56px; height: 56px; } .ro-slots.big .doll-slot .gi { width: 36px; height: 36px; }
|
||||
.compose-grid { display: grid; grid-template-columns: 1fr 1.3fr .8fr; gap: 26px; align-items: start; }
|
||||
.compose select { width: 100%; }
|
||||
.ro-pick { display: flex; align-items: center; gap: 10px; margin-top: 10px; }
|
||||
.ro-pick .doll-slot { cursor: default; }
|
||||
.ro-pick > .doll-slot .gi { width: 34px; height: 34px; } .ro-pick .small .gi { width: 14px; height: 14px; }
|
||||
.stepper { display: flex; align-items: center; gap: 8px; margin-top: 10px; }
|
||||
.stepper input { width: 90px; }
|
||||
.reward-input { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
|
||||
.reward-input input { width: 110px; }
|
||||
@media (max-width: 900px) { .compose-grid { grid-template-columns: 1fr; gap: 14px; } }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue