chore: bootstrap lean sysadmin-chronicles repo
Import the runnable game code, content, docs, scripts, and repo guidance while leaving local agent state, dependency installs, build output, and backup copies out of the published tree.
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
<script>
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
export let mail = [];
|
||||
export let selectedMail = null;
|
||||
export let busy = false;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
$: unreadCount = mail.filter((item) => !item.read).length;
|
||||
</script>
|
||||
|
||||
<section class="panel-grid">
|
||||
<aside class="list">
|
||||
<header>
|
||||
<h2>Mail</h2>
|
||||
<div class="mail-meta">
|
||||
<span>{mail.length} threads</span>
|
||||
{#if unreadCount > 0}
|
||||
<span class="badge unread-summary">{unreadCount} unread</span>
|
||||
{/if}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{#if mail.length === 0}
|
||||
<p class="empty">No mail yet.</p>
|
||||
{:else}
|
||||
{#each mail as item}
|
||||
<button class:selected={item.id === selectedMail?.id} class:has-unread={!item.read} on:click={() => dispatch('select', item.id)}>
|
||||
<div class="topline">
|
||||
<strong>{item.subject}</strong>
|
||||
{#if !item.read}<span class="badge unread">unread</span>{/if}
|
||||
</div>
|
||||
<div>{item.from}</div>
|
||||
<small>{new Date(item.timestamp).toLocaleString()}</small>
|
||||
</button>
|
||||
{/each}
|
||||
{/if}
|
||||
</aside>
|
||||
|
||||
<article class="detail">
|
||||
{#if selectedMail}
|
||||
<div class="mail-header">
|
||||
<div>
|
||||
<h3>{selectedMail.subject}</h3>
|
||||
<p>{selectedMail.from}</p>
|
||||
</div>
|
||||
<span class={`badge ${selectedMail.read ? 'read' : 'unread'}`}>
|
||||
{selectedMail.read ? 'Read' : 'Unread'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="body">{selectedMail.body}</div>
|
||||
|
||||
{#if selectedMail.attachments?.length}
|
||||
<section class="attachments">
|
||||
<h4>Attachments</h4>
|
||||
<div class="chips">
|
||||
{#each selectedMail.attachments as attachment}
|
||||
<button on:click={() => dispatch('attachment', attachment)}>{attachment}</button>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
{#if selectedMail.reply_options?.length && !selectedMail.replied}
|
||||
<section class="reply-block">
|
||||
<h4>Reply</h4>
|
||||
<div class="chips">
|
||||
{#each selectedMail.reply_options as option, index}
|
||||
<button disabled={busy} on:click={() => dispatch('reply', { id: selectedMail.id, choice: index })}>
|
||||
{option.label}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
{:else}
|
||||
<p class="empty">Select a message to read it.</p>
|
||||
{/if}
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.panel-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(280px, 360px) minmax(0, 1fr);
|
||||
gap: 1rem;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.list,
|
||||
.detail {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 24px;
|
||||
padding: 1rem;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.list {
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
header,
|
||||
.topline {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mail-meta,
|
||||
.mail-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h2, h3, h4 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.list button,
|
||||
.chips button {
|
||||
text-align: left;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 16px;
|
||||
padding: 0.9rem;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.list button.selected {
|
||||
border-color: rgba(214, 104, 56, 0.45);
|
||||
background: rgba(214, 104, 56, 0.1);
|
||||
}
|
||||
|
||||
.list button.has-unread {
|
||||
box-shadow: inset 3px 0 0 rgba(214, 104, 56, 0.75);
|
||||
}
|
||||
|
||||
.badge {
|
||||
font-size: 0.72rem;
|
||||
text-transform: uppercase;
|
||||
padding: 0.18rem 0.5rem;
|
||||
border-radius: 999px;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
.unread,
|
||||
.unread-summary {
|
||||
background: rgba(214, 104, 56, 0.18);
|
||||
color: #f8e5d7;
|
||||
}
|
||||
|
||||
.read {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.body {
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.6;
|
||||
color: #f4eee4;
|
||||
}
|
||||
|
||||
p,
|
||||
small {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.attachments,
|
||||
.reply-block {
|
||||
margin-top: 1.25rem;
|
||||
}
|
||||
|
||||
.chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.7rem;
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.panel-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user