personal memory agent
1<style>
2.reflection-shell {
3 max-width: 900px;
4 margin: 0 auto;
5 padding: 2rem 2rem 5rem;
6 color: #1f2937;
7}
8
9.reflection-header {
10 display: flex;
11 justify-content: space-between;
12 gap: 1rem;
13 align-items: flex-start;
14 margin-bottom: 1.5rem;
15}
16
17.reflection-kicker {
18 margin: 0 0 0.35rem;
19 font-size: 0.85rem;
20 font-weight: 600;
21 letter-spacing: 0.08em;
22 text-transform: uppercase;
23 color: #92400e;
24}
25
26.reflection-title {
27 margin: 0;
28 font-size: clamp(1.8rem, 4vw, 2.7rem);
29 line-height: 1.1;
30 color: #111827;
31}
32
33.reflection-subtitle {
34 margin: 0.35rem 0 0;
35 font-size: 1rem;
36 color: #6b7280;
37}
38
39.reflection-actions {
40 display: flex;
41 gap: 0.75rem;
42 flex-wrap: wrap;
43}
44
45.reflection-button,
46.reflection-link-button {
47 display: inline-flex;
48 align-items: center;
49 justify-content: center;
50 min-height: 42px;
51 padding: 0 1rem;
52 border-radius: 999px;
53 border: 1px solid #d6d3d1;
54 background: #fffdf8;
55 color: #1f2937;
56 font: inherit;
57 text-decoration: none;
58 cursor: pointer;
59 transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
60}
61
62.reflection-button:hover,
63.reflection-link-button:hover {
64 background: #fef3c7;
65 border-color: #f59e0b;
66}
67
68.reflection-body,
69.reflection-index {
70 background: #fff;
71 border: 1px solid #e7e5e4;
72 border-radius: 18px;
73 padding: 1.5rem;
74 box-shadow: 0 20px 40px rgba(15, 23, 42, 0.04);
75}
76
77.reflection-body h1,
78.reflection-body h2,
79.reflection-body h3 {
80 color: #111827;
81}
82
83.reflection-body p,
84.reflection-body li,
85.reflection-index p,
86.reflection-index li {
87 line-height: 1.7;
88}
89
90.reflection-body blockquote {
91 margin: 1.5rem 0;
92 padding-left: 1rem;
93 border-left: 3px solid #f59e0b;
94 color: #6b7280;
95}
96
97.reflection-body a,
98.reflection-index a {
99 color: #b45309;
100}
101
102.reflection-body a:hover,
103.reflection-index a:hover {
104 color: #92400e;
105}
106
107.reflection-index-list {
108 list-style: none;
109 margin: 1.25rem 0 0;
110 padding: 0;
111 display: grid;
112 gap: 0.75rem;
113}
114
115.reflection-index-link {
116 display: flex;
117 justify-content: space-between;
118 gap: 1rem;
119 align-items: center;
120 padding: 1rem 1.1rem;
121 border-radius: 14px;
122 background: #fffbeb;
123 border: 1px solid #fcd34d;
124 text-decoration: none;
125 color: #1f2937;
126}
127
128.reflection-index-link:hover {
129 background: #fef3c7;
130}
131
132.reflection-index-day {
133 font-weight: 600;
134}
135
136.reflection-index-token {
137 color: #78716c;
138 font-size: 0.9rem;
139}
140
141@media (max-width: 720px) {
142 .reflection-shell {
143 padding: 1.25rem 1rem 4rem;
144 }
145
146 .reflection-header {
147 flex-direction: column;
148 }
149
150 .reflection-actions {
151 width: 100%;
152 }
153}
154</style>
155
156<div class="reflection-shell">
157 {% if view_mode == "index" %}
158 <header class="reflection-header">
159 <div>
160 <p class="reflection-kicker">weekly reflection</p>
161 <h2 class="reflection-title">reflections</h2>
162 <p class="reflection-subtitle">Available weekly reflections, newest first.</p>
163 </div>
164 </header>
165 <section class="reflection-index">
166 {% if weeks %}
167 <ul class="reflection-index-list">
168 {% for week in weeks %}
169 <li>
170 <a class="reflection-index-link" href="{{ week.url }}">
171 <span class="reflection-index-day">week of {{ week.label }}</span>
172 <span class="reflection-index-token">{{ week.day }}</span>
173 </a>
174 </li>
175 {% endfor %}
176 </ul>
177 {% else %}
178 <p>No weekly reflections yet.</p>
179 {% endif %}
180 </section>
181 {% else %}
182 <header class="reflection-header">
183 <div>
184 <p class="reflection-kicker">weekly reflection</p>
185 <h2 class="reflection-title">{{ reflection_week_label }}</h2>
186 <p class="reflection-subtitle">week of {{ reflection_week_label }}</p>
187 </div>
188 <div class="reflection-actions">
189 <button type="button" class="reflection-button" id="copyReflectionButton">copy</button>
190 <a class="reflection-link-button" href="{{ pdf_url }}">download PDF</a>
191 </div>
192 </header>
193
194 <section class="reflection-body" id="reflectionBody"></section>
195 {% endif %}
196</div>
197
198{% if view_mode == "detail" %}
199<script>
200(() => {
201 const markdownSource = {{ reflection_markdown|tojson|safe }};
202 const reflectionBody = document.getElementById('reflectionBody');
203 const copyButton = document.getElementById('copyReflectionButton');
204 const rawUrl = {{ raw_url|tojson|safe }};
205
206 function copyText(text) {
207 if (navigator.clipboard && navigator.clipboard.writeText) {
208 return navigator.clipboard.writeText(text);
209 }
210
211 const area = document.createElement('textarea');
212 area.value = text;
213 area.style.position = 'fixed';
214 area.style.opacity = '0';
215 document.body.appendChild(area);
216 area.select();
217 document.execCommand('copy');
218 document.body.removeChild(area);
219 return Promise.resolve();
220 }
221
222 if (reflectionBody) {
223 reflectionBody.innerHTML = window.AppServices.renderMarkdown(markdownSource);
224 }
225
226 if (copyButton) {
227 copyButton.addEventListener('click', async () => {
228 const originalLabel = copyButton.textContent;
229 try {
230 const response = await fetch(rawUrl, { credentials: 'same-origin' });
231 if (!response.ok) throw new Error('copy failed');
232 await copyText(await response.text());
233 copyButton.textContent = 'copied';
234 } catch (_error) {
235 copyButton.textContent = 'failed';
236 } finally {
237 window.setTimeout(() => {
238 copyButton.textContent = originalLabel;
239 }, 2000);
240 }
241 });
242 }
243})();
244</script>
245{% endif %}