ProjectIntakeTower / index.html
smlflg's picture
Initial public upload from Projekte/ProjectIntakeTower
2ad02e3 verified
Raw History Blame Contribute Delete
8.63 kB
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Owner Packet Viewer</title>
<style>
:root {
color-scheme: dark;
font-family:
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: #111417;
color: #eef2ef;
}
* {
box-sizing: border-box;
}
body {
min-height: 100vh;
margin: 0;
background: #111417;
}
button,
textarea {
font: inherit;
}
.shell {
min-height: 100vh;
display: grid;
grid-template-rows: auto minmax(0, 1fr);
gap: 16px;
padding: 18px;
}
header,
main {
width: min(980px, 100%);
margin: 0 auto;
}
header {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 16px;
align-items: end;
border-bottom: 1px solid #2c3533;
padding-bottom: 14px;
}
.eyebrow,
.label {
color: #93a09b;
font-size: 0.73rem;
font-weight: 800;
letter-spacing: 0;
text-transform: uppercase;
}
h1 {
margin: 4px 0 0;
font-size: clamp(1.5rem, 3vw, 2.35rem);
line-height: 1.08;
}
.status {
min-width: 150px;
border: 1px solid #40514d;
border-radius: 8px;
padding: 10px 12px;
color: #a7f0dd;
text-align: center;
font-weight: 800;
}
main {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(260px, 320px);
gap: 16px;
align-items: start;
}
.packet,
.side {
border: 1px solid #293331;
border-radius: 8px;
background: #171d20;
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.24);
}
.packet {
padding: 22px;
}
.question {
margin: 8px 0 14px;
font-size: 1.42rem;
line-height: 1.22;
}
.context {
margin: 0;
color: #c7d0cb;
line-height: 1.55;
}
.field {
display: grid;
gap: 7px;
margin-top: 18px;
}
.value {
border-left: 3px solid #6de0c9;
padding: 11px 13px;
background: #20322f;
color: #f6fbf9;
line-height: 1.45;
}
.risk {
border-left-color: #ffc56f;
background: #33291b;
}
.actions {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 10px;
margin-top: 22px;
}
.action {
min-height: 44px;
border: 0;
border-radius: 8px;
color: #06110e;
font-weight: 850;
cursor: pointer;
}
.action:disabled {
cursor: default;
opacity: 0.45;
}
.action.selected {
box-shadow: 0 0 0 3px rgba(238, 242, 239, 0.32);
}
[data-action="Approve"] {
background: #6de0c9;
}
[data-action="Reject"] {
background: #ff8d8d;
}
[data-action="Ask"] {
background: #ffc56f;
}
.side {
display: grid;
gap: 16px;
padding: 18px;
}
textarea {
min-height: 180px;
resize: vertical;
border: 1px solid #40514d;
border-radius: 8px;
padding: 12px;
outline: none;
background: #0d1113;
color: #eef2ef;
}
textarea:focus {
border-color: #6de0c9;
box-shadow: 0 0 0 3px rgba(109, 224, 201, 0.13);
}
.decision {
min-height: 48px;
border: 1px solid #40514d;
border-radius: 8px;
padding: 12px;
color: #c7d0cb;
}
.error {
color: #ffabab;
}
@media (max-width: 760px) {
header,
main {
grid-template-columns: 1fr;
}
.status {
text-align: left;
}
.actions {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="shell">
<header>
<div>
<div class="eyebrow">Owner Packet</div>
<h1 id="project">Loading packet</h1>
</div>
<div class="status" id="status">loading</div>
</header>
<main>
<section class="packet" aria-label="Owner decision packet">
<div class="label">Question</div>
<h2 class="question" id="question">Loading owner_packet.json...</h2>
<p class="context" id="context"></p>
<div class="field">
<div class="label">Recommended action</div>
<div class="value" id="recommendedAction"></div>
</div>
<div class="field">
<div class="label">Risk</div>
<div class="value risk" id="risk"></div>
</div>
<div class="actions" id="actions" aria-label="Decision actions"></div>
</section>
<aside class="side" aria-label="Local owner note">
<div>
<div class="label">Local decision</div>
<div class="decision" id="decision">No decision selected.</div>
</div>
<label class="label" for="note">Local note</label>
<textarea id="note" placeholder="Private local note. Not written back to owner_packet.json."></textarea>
</aside>
</main>
</div>
<script>
const noteKey = "project-intake-tower.owner-note";
const selectedKey = "project-intake-tower.owner-decision";
const elements = {
project: document.getElementById("project"),
status: document.getElementById("status"),
question: document.getElementById("question"),
context: document.getElementById("context"),
recommendedAction: document.getElementById("recommendedAction"),
risk: document.getElementById("risk"),
actions: document.getElementById("actions"),
decision: document.getElementById("decision"),
note: document.getElementById("note")
};
elements.note.value = localStorage.getItem(noteKey) || "";
elements.note.addEventListener("input", () => {
localStorage.setItem(noteKey, elements.note.value);
});
fetch("owner_packet.json", { cache: "no-store" })
.then((response) => {
if (!response.ok) {
throw new Error("owner_packet.json could not be loaded");
}
return response.json();
})
.then(renderPacket)
.catch((error) => {
elements.status.textContent = "error";
elements.question.textContent = "Packet could not be loaded.";
elements.context.textContent = error.message;
elements.context.classList.add("error");
});
function renderPacket(packet) {
elements.project.textContent = packet.project || "Unnamed project";
elements.status.textContent = packet.status || "unknown";
elements.question.textContent = packet.question || "No question provided.";
elements.context.textContent = packet.context || "No context provided.";
elements.recommendedAction.textContent = packet.recommended_action || "No recommendation provided.";
elements.risk.textContent = packet.risk || "No risk provided.";
renderActions(Array.isArray(packet.options) ? packet.options : ["Approve", "Reject", "Ask"]);
}
function renderActions(options) {
elements.actions.innerHTML = "";
const savedDecision = localStorage.getItem(selectedKey);
for (const option of options) {
const button = document.createElement("button");
button.type = "button";
button.className = "action";
button.dataset.action = option;
button.textContent = option;
button.addEventListener("click", () => selectDecision(option));
elements.actions.appendChild(button);
}
if (savedDecision) {
selectDecision(savedDecision);
}
}
function selectDecision(decision) {
localStorage.setItem(selectedKey, decision);
elements.decision.textContent = decision;
for (const button of elements.actions.querySelectorAll("button")) {
const selected = button.dataset.action === decision;
button.classList.toggle("selected", selected);
button.disabled = true;
}
}
</script>
</body>
</html>