<!
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Journal</title>
<link href="[Link]
al,wght@0,400;0,700;1,400&family=DM+Sans:wght@300;400;500;600&
family=DM+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
:root {
--bg: #f7f3ee;
--surface: #ffffff;
--surface2: #f0ebe4;
--border: #e0d8ce;
--text: #2c2420;
--text2: #7a6e68;
--accent: #c17f4a;
--accent2: #8b5e3c;
--accent-light: #f5e6d3;
--danger: #c0392b;
--success: #27ae60;
--tag-bg: #e8dfd4;
--shadow: 0 2px 16px rgba(44,36,32,0.08);
--shadow-lg: 0 8px 40px rgba(44,36,32,0.14);
--radius: 14px;
--font-display: 'Playfair Display', Georgia, serif;
--font-body: 'DM Sans', sans-serif;
--font-mono: 'DM Mono', monospace;
--sidebar-w: 240px;
}
.dark {
--bg: #1a1612;
--surface: #241f1b;
--surface2: #2e2822;
--border: #3d342c;
--text: #f0e8df;
--text2: #a89b8f;
--accent: #d4914f;
--accent2: #e8a86b;
--accent-light: #3a2d20;
--tag-bg: #3a2d22;
--shadow: 0 2px 16px rgba(0,0,0,0.3);
--shadow-lg: 0 8px 40px rgba(0,0,0,0.4);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: var(--font-body);
background: var(--bg);
color: var(--text);
min-height: 100vh;
transition: background 0.3s, color 0.3s;
display: flex;
flex-direction: column;
}
/* ---- SIDEBAR ---- */
#sidebar {
position: fixed; top: 0; left: 0; bottom: 0;
width: var(--sidebar-w);
background: var(--surface);
border-right: 1px solid var(--border);
display: flex; flex-direction: column;
z-index: 100;
transition: background 0.3s, border-color 0.3s;
box-shadow: var(--shadow);
}
.sidebar-logo {
padding: 28px 24px 20px;
border-bottom: 1px solid var(--border);
}
.active-filter { background: var(--accent-light) !important; color:
var(--accent) !important; border-color: var(--accent) !important;
font-weight: 700; }
.sidebar-logo h1 {
font-family: var(--font-display);
font-size: 1.5rem;
color: var(--accent);
letter-spacing: 0.01em;
line-height: 1;
}
.sidebar-logo p {
font-size: 0.72rem;
color: var(--text2);
margin-top: 4px;
font-weight: 500;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.sidebar-nav { flex: 1; padding: 12px 10px; overflow-y: auto; }
.nav-item {
display: flex; align-items: center; gap: 10px;
padding: 10px 14px;
border-radius: 10px;
cursor: pointer;
font-size: 0.88rem;
font-weight: 500;
color: var(--text2);
transition: all 0.2s;
margin-bottom: 2px;
border: none; background: none; width: 100%; text-align: left;
}
.nav-item:hover { background: var(--surface2); color: var(--text); }
.[Link] { background: var(--accent-light); color: var(--accent); }
.nav-item .icon { font-size: 1.1rem; width: 22px; text-align: center; }
.sidebar-footer {
padding: 14px 10px;
border-top: 1px solid var(--border);
}
.theme-toggle {
display: flex; align-items: center; justify-content: space-between;
padding: 10px 14px;
border-radius: 10px;
background: var(--surface2);
cursor: pointer;
font-size: 0.82rem;
font-weight: 500;
color: var(--text2);
border: none; width: 100%;
}
.toggle-pill {
width: 36px; height: 20px;
background: var(--border);
border-radius: 20px;
position: relative;
transition: background 0.3s;
}
.[Link] { background: var(--accent); }
.toggle-knob {
position: absolute; top: 2px; left: 2px;
width: 16px; height: 16px;
background: white;
border-radius: 50%;
transition: left 0.3s;
box-shadow: 0 1px 4px rgba(0,0,0,0.2);
}
.[Link] .toggle-knob { left: 18px; }
/* ---- MAIN ---- */
#main {
margin-left: var(--sidebar-w);
flex: 1;
min-height: 100vh;
display: flex;
flex-direction: column;
}
.topbar {
position: sticky; top: 0; z-index: 50;
background: var(--bg);
border-bottom: 1px solid var(--border);
padding: 16px 32px;
display: flex; align-items: center; justify-content: space-between;
transition: background 0.3s;
}
.topbar h2 {
font-family: var(--font-display);
font-size: 1.4rem;
color: var(--text);
}
.topbar-meta {
font-size: 0.8rem;
color: var(--text2);
font-family: var(--font-mono);
}
.page { display: none; padding: 32px; max-width: 900px; margin: 0
auto; width: 100%; }
.[Link] { display: block; }
/* ---- BUTTONS ---- */
.btn {
display: inline-flex; align-items: center; gap: 6px;
padding: 9px 18px;
border-radius: 9px;
font-size: 0.85rem;
font-weight: 600;
font-family: var(--font-body);
cursor: pointer;
border: none;
transition: all 0.18s;
}
.btn-primary { background: var(--accent); color: white; }
.btn-primary:hover { background: var(--accent2); }
.btn-outline {
background: transparent;
color: var(--text2);
border: 1.5px solid var(--border);
}
.btn-outline:hover { border-color: var(--accent); color: var(--accent);
background: var(--accent-light); }
.btn-danger { background: var(--danger); color: white; }
.btn-danger:hover { opacity: 0.85; }
.btn-sm { padding: 6px 12px; font-size: 0.78rem; border-radius: 7px; }
.btn-ghost { background: transparent; color: var(--text2); border: none;
padding: 6px 10px; }
.btn-ghost:hover { color: var(--accent); background: var(--accent-light);
}
/* ---- FORM ELEMENTS ---- */
.form-group { margin-bottom: 18px; }
.form-label {
display: block;
font-size: 0.78rem;
font-weight: 600;
color: var(--text2);
text-transform: uppercase;
letter-spacing: 0.07em;
margin-bottom: 7px;
}
input[type="text"], input[type="date"], textarea, select {
width: 100%;
padding: 10px 14px;
border: 1.5px solid var(--border);
border-radius: 9px;
font-size: 0.9rem;
font-family: var(--font-body);
background: var(--surface);
color: var(--text);
transition: border-color 0.2s, background 0.3s;
outline: none;
}
input:focus, textarea:focus, select:focus { border-color: var(--accent); }
textarea { resize: vertical; min-height: 140px; line-height: 1.7; }
select { cursor: pointer; }
/* ---- NEW ENTRY ---- */
.entry-meta-row {
display: grid; grid-template-columns: 1fr 1fr; gap: 14px;
margin-bottom: 14px;
}
.entry-meta-row-3 {
display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 14px;
margin-bottom: 14px;
}
.datetime-display {
background: var(--surface2);
padding: 10px 14px;
border-radius: 9px;
font-family: var(--font-mono);
font-size: 0.82rem;
color: var(--accent);
border: 1.5px solid var(--border);
display: flex; align-items: center; gap: 8px;
}
.timer-display {
background: var(--accent-light);
border: 1.5px solid var(--accent);
border-radius: 9px;
padding: 10px 14px;
font-family: var(--font-mono);
font-size: 0.82rem;
color: var(--accent);
display: flex; align-items: center; gap: 8px;
}
.mood-grid {
display: flex; flex-wrap: wrap; gap: 8px;
margin-bottom: 4px;
}
.mood-chip {
padding: 7px 13px;
border-radius: 20px;
font-size: 0.82rem;
cursor: pointer;
border: 1.5px solid var(--border);
background: var(--surface);
color: var(--text2);
transition: all 0.18s;
font-weight: 500;
}
.mood-chip:hover { border-color: var(--accent); color: var(--accent); }
.[Link] { background: var(--accent); color: white;
border-color: var(--accent); }
.tags-input-wrap {
display: flex; flex-wrap: wrap; gap: 6px;
padding: 8px 10px;
border: 1.5px solid var(--border);
border-radius: 9px;
background: var(--surface);
cursor: text;
min-height: 44px;
align-items: center;
}
.tags-input-wrap:focus-within { border-color: var(--accent); }
.tag-pill {
display: inline-flex; align-items: center; gap: 4px;
padding: 3px 10px 3px 10px;
background: var(--tag-bg);
color: var(--accent2);
border-radius: 20px;
font-size: 0.78rem;
font-weight: 600;
}
.tag-pill button { background: none; border: none; cursor: pointer; color:
var(--text2); font-size: 0.9rem; padding: 0 0 0 2px; line-height: 1; }
.tag-pill button:hover { color: var(--danger); }
.tags-input {
border: none; outline: none; background: transparent;
font-size: 0.85rem; font-family: var(--font-body);
color: var(--text); min-width: 100px; flex: 1;
padding: 2px 4px;
}
.custom-questions-section {
background: var(--surface2);
border-radius: var(--radius);
padding: 16px 18px;
margin-bottom: 18px;
border: 1px solid var(--border);
}
.custom-questions-section h4 {
font-size: 0.78rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--text2);
margin-bottom: 12px;
}
.cq-item { margin-bottom: 14px; }
.cq-item:last-child { margin-bottom: 0; }
.cq-label { font-size: 0.85rem; font-weight: 600; color: var(--text);
margin-bottom: 6px; }
/* ---- ENTRIES LIST ---- */
.entries-list { display: flex; flex-direction: column; gap: 14px; }
.entry-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 20px 22px;
cursor: pointer;
transition: all 0.2s;
box-shadow: var(--shadow);
}
.entry-card:hover { border-color: var(--accent); box-shadow:
var(--shadow-lg); transform: translateY(-1px); }
.entry-card-header {
display: flex; align-items: flex-start; justify-content: space-between;
margin-bottom: 10px;
}
.entry-card-title {
font-family: var(--font-display);
font-size: 1.08rem;
font-weight: 700;
color: var(--text);
flex: 1;
}
.entry-card-meta {
display: flex; flex-wrap: wrap; gap: 8px;
align-items: center;
margin-top: 4px;
}
.meta-badge {
font-size: 0.73rem;
font-weight: 600;
padding: 2px 9px;
border-radius: 20px;
background: var(--surface2);
color: var(--text2);
font-family: var(--font-mono);
}
.[Link] { background: var(--accent-light); color:
var(--accent); }
.[Link] { background: var(--tag-bg); color: var(--accent2); }
.entry-card-preview {
font-size: 0.85rem;
color: var(--text2);
line-height: 1.6;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.entry-card-actions {
display: flex; gap: 6px;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid var(--border);
}
.entry-checkbox { margin-right: 6px; accent-color: var(--accent); width:
16px; height: 16px; cursor: pointer; }
/* ---- MODAL ---- */
.modal-overlay {
position: fixed; inset: 0;
background: rgba(0,0,0,0.45);
z-index: 200;
display: none; align-items: center; justify-content: center;
padding: 20px;
backdrop-filter: blur(4px);
}
.[Link] { display: flex; }
.modal {
background: var(--surface);
border-radius: 18px;
padding: 28px;
width: 100%; max-width: 680px;
max-height: 85vh;
overflow-y: auto;
box-shadow: var(--shadow-lg);
position: relative;
}
.modal-title {
font-family: var(--font-display);
font-size: 1.4rem;
margin-bottom: 18px;
color: var(--text);
}
.modal-close {
position: absolute; top: 18px; right: 20px;
background: none; border: none; font-size: 1.4rem;
cursor: pointer; color: var(--text2);
width: 32px; height: 32px;
display: flex; align-items: center; justify-content: center;
border-radius: 50%;
transition: all 0.2s;
}
.modal-close:hover { background: var(--surface2); color: var(--text); }
/* ---- STATS ---- */
.stats-grid {
display: grid; grid-template-columns: repeat(auto-fit, minmax(180px,
1fr));
gap: 14px; margin-bottom: 24px;
}
.stat-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 18px 20px;
text-align: center;
box-shadow: var(--shadow);
}
.stat-value {
font-family: var(--font-display);
font-size: 2rem;
font-weight: 700;
color: var(--accent);
line-height: 1;
margin-bottom: 6px;
}
.stat-label { font-size: 0.78rem; color: var(--text2); font-weight: 600;
text-transform: uppercase; letter-spacing: 0.06em; }
.chart-section {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 20px 22px;
margin-bottom: 16px;
box-shadow: var(--shadow);
}
.chart-section h3 {
font-family: var(--font-display);
font-size: 1rem;
color: var(--text);
margin-bottom: 14px;
}
.bar-chart { display: flex; flex-direction: column; gap: 8px; }
.bar-row { display: flex; align-items: center; gap: 10px; }
.bar-label { width: 90px; font-size: 0.78rem; color: var(--text2);
text-align: right; flex-shrink: 0; }
.bar-track { flex: 1; background: var(--surface2); border-radius: 6px;
height: 22px; overflow: hidden; }
.bar-fill { height: 100%; background: linear-gradient(90deg,
var(--accent), var(--accent2)); border-radius: 6px; transition: width 0.6s
ease; display: flex; align-items: center; padding-left: 8px; }
.bar-val { font-size: 0.72rem; color: white; font-weight: 600; }
/* ---- SEARCH ---- */
.search-bar-wrap { position: relative; margin-bottom: 18px; }
.search-bar-wrap input { padding-left: 38px; }
.search-icon { position: absolute; left: 12px; top: 50%; transform:
translateY(-50%); color: var(--text2); font-size: 1rem; pointer-events:
none; }
.filter-row {
display: flex; flex-wrap: wrap; gap: 10px;
margin-bottom: 18px;
padding: 14px 16px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
}
.filter-row select, .filter-row input {
flex: 1; min-width: 130px; max-width: 180px;
font-size: 0.82rem; padding: 7px 10px;
}
/* ---- SETTINGS ---- */
.settings-section {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 22px 24px;
margin-bottom: 18px;
box-shadow: var(--shadow);
}
.settings-section h3 {
font-family: var(--font-display);
font-size: 1rem;
margin-bottom: 16px;
color: var(--text);
padding-bottom: 10px;
border-bottom: 1px solid var(--border);
}
.setting-row {
display: flex; align-items: center; justify-content: space-between;
padding: 10px 0;
border-bottom: 1px solid var(--border);
}
.setting-row:last-child { border-bottom: none; }
.setting-info h4 { font-size: 0.88rem; font-weight: 600; color: var(--text);
}
.setting-info p { font-size: 0.78rem; color: var(--text2); margin-top: 2px; }
.cq-list { display: flex; flex-direction: column; gap: 8px; margin-top:
10px; }
.cq-list-item {
display: flex; align-items: center; justify-content: space-between;
padding: 9px 12px;
background: var(--surface2);
border-radius: 8px;
font-size: 0.85rem;
}
.cat-list { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.cat-badge {
display: inline-flex; align-items: center; gap: 5px;
padding: 5px 12px;
background: var(--tag-bg);
color: var(--accent2);
border-radius: 20px;
font-size: 0.8rem;
font-weight: 600;
}
.cat-badge button { background: none; border: none; cursor: pointer;
color: var(--text2); font-size: 0.85rem; }
.cat-badge button:hover { color: var(--danger); }
/* ---- BACKUP ---- */
.backup-section {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 22px 24px;
margin-bottom: 18px;
box-shadow: var(--shadow);
}
.backup-section h3 {
font-family: var(--font-display);
font-size: 1rem;
margin-bottom: 14px;
color: var(--text);
padding-bottom: 10px;
border-bottom: 1px solid var(--border);
}
.backup-options { display: flex; flex-wrap: wrap; gap: 10px;
margin-bottom: 14px; }
.bulk-select-bar {
display: flex; align-items: center; gap: 10px;
padding: 10px 14px;
background: var(--accent-light);
border: 1px solid var(--accent);
border-radius: 9px;
margin-bottom: 14px;
font-size: 0.85rem;
color: var(--accent);
font-weight: 600;
}
.empty-state {
text-align: center;
padding: 50px 20px;
color: var(--text2);
}
.empty-state .icon { font-size: 3rem; margin-bottom: 12px; }
.empty-state p { font-size: 0.9rem; }
/* ---- DETAIL VIEW ---- */
.detail-content {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 28px 32px;
box-shadow: var(--shadow);
white-space: pre-wrap;
line-height: 1.8;
font-size: 0.95rem;
}
.detail-meta { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom:
18px; }
.detail-qa {
background: var(--surface2);
border-radius: 10px;
padding: 14px 16px;
margin-bottom: 18px;
}
.detail-qa h4 { font-size: 0.8rem; font-weight: 700; text-transform:
uppercase; letter-spacing: 0.07em; color: var(--text2); margin-bottom:
8px; }
.qa-pair { margin-bottom: 8px; }
.qa-pair .q { font-size: 0.82rem; font-weight: 600; color: var(--text2); }
.qa-pair .a { font-size: 0.88rem; color: var(--text); margin-top: 2px; }
.toast {
position: fixed; bottom: 24px; right: 24px;
background: var(--text);
color: var(--bg);
padding: 12px 20px;
border-radius: 10px;
font-size: 0.85rem;
font-weight: 600;
z-index: 999;
box-shadow: var(--shadow-lg);
transform: translateY(80px);
opacity: 0;
transition: all 0.3s;
}
.[Link] { transform: translateY(0); opacity: 1; }
.divider { height: 1px; background: var(--border); margin: 18px 0; }
.flask-status-badge {
display: inline-block;
font-size: 0.75rem;
font-weight: 600;
padding: 3px 10px;
border-radius: 20px;
background: var(--surface2);
color: var(--text2);
border: 1px solid var(--border);
}
.[Link] { background: rgba(39,174,96,0.1); color:
var(--success); border-color: var(--success); }
.[Link] { background: rgba(192,57,43,0.1); color:
var(--danger); border-color: var(--danger); }
.add-inline-wrap { display: flex; gap: 8px; margin-top: 10px; }
.add-inline-wrap input { flex: 1; }
.section-header {
display: flex; align-items: center; justify-content: space-between;
margin-bottom: 20px;
}
.section-header h2 { font-family: var(--font-display); font-size: 1.5rem;
color: var(--text); }
/* ---- QUESTION SESSION ---- */
.q-session-card {
background: var(--surface);
border: 1.5px solid var(--border);
border-radius: var(--radius);
padding: 20px 22px;
margin-bottom: 16px;
box-shadow: var(--shadow);
transition: border-color 0.2s;
}
.[Link]-q { border-color: var(--accent); box-shadow: 0 0
0 3px var(--accent-light); }
.q-session-header {
display: flex; align-items: center; justify-content: space-between;
margin-bottom: 12px; gap: 10px;
}
.q-number {
width: 28px; height: 28px; border-radius: 50%;
background: var(--accent); color: white;
font-size: 0.78rem; font-weight: 700;
display: flex; align-items: center; justify-content: center;
flex-shrink: 0;
}
.q-text { font-size: 0.95rem; font-weight: 600; color: var(--text); flex: 1;
line-height: 1.4; }
.q-timer-badge {
display: flex; align-items: center; gap: 5px;
padding: 4px 10px;
border-radius: 20px;
background: var(--surface2);
border: 1.5px solid var(--border);
font-family: var(--font-mono);
font-size: 0.78rem;
color: var(--text2);
flex-shrink: 0;
transition: all 0.3s;
}
.[Link] { background: var(--accent-light); border-color:
var(--accent); color: var(--accent); }
.[Link] { background: #e8f5e9; border-color:
var(--success); color: var(--success); }
.dark .[Link] { background: #1a3a1f; }
.q-timer-dot { width: 7px; height: 7px; border-radius: 50%; background:
var(--text2); }
.[Link] .q-timer-dot { background: var(--accent);
animation: pulse-dot 1s infinite; }
.[Link] .q-timer-dot { background: var(--success); }
@keyframes pulse-dot { 0%,100%{opacity:1;} 50%{opacity:0.3;} }
.q-controls { display: flex; gap: 8px; margin-top: 10px; align-items:
center; flex-wrap: wrap; }
.q-status-line {
font-size: 0.75rem; color: var(--text2);
margin-top: 6px;
font-style: italic;
}
.session-summary {
background: var(--accent-light);
border: 1.5px solid var(--accent);
border-radius: var(--radius);
padding: 18px 20px;
margin-bottom: 20px;
display: none;
}
.session-summary h3 { font-family: var(--font-display); font-size: 1rem;
color: var(--accent); margin-bottom: 12px; }
.summary-row { display: flex; justify-content: space-between; font-size:
0.85rem; padding: 4px 0; border-bottom: 1px solid var(--border); }
.summary-row:last-child { border-bottom: none; font-weight: 700; }
.no-questions-msg {
text-align: center; padding: 50px 20px; color: var(--text2);
}
.no-questions-msg .icon { font-size: 2.5rem; margin-bottom: 10px; }
/* ---- STAR / FAVOURITE ---- */
.star-btn {
background: none; border: none; cursor: pointer;
font-size: 1.2rem; padding: 2px 6px;
color: var(--border);
transition: color 0.2s, transform 0.2s;
line-height: 1;
}
.[Link] { color: #f5c518; }
.star-btn:hover { transform: scale(1.2); }
/* ---- ERA SYSTEM ---- */
.era-timeline { position: relative; padding-left: 24px; }
.era-timeline::before { content: ''; position: absolute; left: 8px; top: 0;
bottom: 0; width: 2px; background: var(--border); }
.era-month-group { margin-bottom: 32px; }
.era-month-label { font-family: var(--font-display); font-size: 1.05rem;
font-weight: 700; color: var(--text); margin-bottom: 12px; padding-left:
8px; }
.era-day-row { display: flex; align-items: stretch; margin-bottom: 6px;
position: relative; }
.era-day-dot { position: absolute; left: -20px; top: 50%; transform:
translateY(-50%); width: 10px; height: 10px; border-radius: 50%;
background: var(--border); border: 2px solid var(--bg); flex-shrink: 0;
z-index: 1; }
.[Link]-entry { background: var(--accent); }
.era-day-card { flex: 1; background: var(--surface); border: 1px solid
var(--border); border-radius: 10px; padding: 10px 14px; display: flex;
align-items: center; gap: 10px; min-height: 46px; transition: border-color
0.2s; }
.era-day-card:hover { border-color: var(--accent); }
.era-day-date { font-family: var(--font-mono); font-size: 0.75rem; color:
var(--text2); flex-shrink: 0; width: 36px; text-align: center; }
.era-day-era-band { width: 6px; border-radius: 4px; align-self: stretch;
flex-shrink: 0; min-height: 20px; }
.era-day-entries { flex: 1; display: flex; flex-direction: column; gap: 3px;
}
.era-day-entry-title { font-size: 0.82rem; font-weight: 600; color:
var(--text); cursor: pointer; white-space: nowrap; overflow: hidden;
text-overflow: ellipsis; }
.era-day-entry-title:hover { color: var(--accent); }
.era-day-entry-title .star-inline { color: #f5c518; font-size: 0.75rem;
margin-right: 3px; }
.era-day-empty { font-size: 0.78rem; color: var(--text2); font-style: italic;
}
.era-label-badge { font-size: 0.7rem; font-weight: 700; padding: 2px
8px; border-radius: 10px; white-space: nowrap; flex-shrink: 0; }
.era-list { display: flex; flex-direction: column; gap: 10px;
margin-bottom: 16px; }
.era-item { display: flex; align-items: center; gap: 10px; background:
var(--surface); border: 1px solid var(--border); border-radius: 10px;
padding: 12px 14px; }
.era-color-dot { width: 14px; height: 14px; border-radius: 50%;
flex-shrink: 0; }
.era-item-info { flex: 1; }
.era-item-name { font-weight: 700; font-size: 0.9rem; color: var(--text); }
.era-item-dates { font-size: 0.75rem; color: var(--text2); font-family:
var(--font-mono); margin-top: 2px; }
.era-add-form { background: var(--surface2); border: 1px solid
var(--border); border-radius: 10px; padding: 14px 16px; display: flex;
flex-direction: column; gap: 10px; }
.era-add-form-row { display: flex; gap: 8px; flex-wrap: wrap;
align-items: center; }
.era-add-form input[type="text"] { flex: 1; min-width: 140px; }
.era-add-form input[type="date"] { flex: 1; min-width: 130px; }
.era-color-picker { display: flex; gap: 6px; flex-wrap: wrap; margin-top:
2px; }
.era-color-opt { width: 22px; height: 22px; border-radius: 50%; border:
2px solid transparent; cursor: pointer; transition: border-color 0.15s,
transform 0.15s; }
.[Link] { border-color: var(--text); transform: scale(1.2);
}
.era-nav-legend { display: flex; flex-wrap: wrap; gap: 6px;
margin-bottom: 16px; }
.era-legend-chip { display: flex; align-items: center; gap: 5px; padding:
4px 10px; border-radius: 20px; font-size: 0.75rem; font-weight: 600;
border: 1.5px solid transparent; cursor: pointer; transition: all 0.18s; }
.[Link] { border-color: var(--text); }
.era-nav-controls { display: flex; align-items: center; gap: 10px;
margin-bottom: 18px; }
.era-nav-controls button { padding: 6px 14px; }
.era-nav-month-label { font-family: var(--font-display); font-size: 1rem;
font-weight: 700; color: var(--text); flex: 1; text-align: center; }
.era-star-filter { display: flex; gap: 6px; margin-bottom: 16px; }
.era-star-filter button { padding: 5px 12px; border-radius: 20px;
font-size: 0.78rem; font-weight: 600; border: 1.5px solid var(--border);
background: none; color: var(--text2); cursor: pointer; transition: all
0.18s; }
.era-star-filter [Link] { background: var(--accent-light); color:
var(--accent); border-color: var(--accent); }
.active-view-btn { background: var(--accent-light) !important; color:
var(--accent) !important; border-color: var(--accent) !important; }
/* ---- ERA GRID ---- */
.era-grid-wrap { display: grid; grid-template-columns: repeat(7, 1fr);
gap: 3px; }
.era-grid-header { display: grid; grid-template-columns: repeat(7, 1fr);
gap: 3px; margin-bottom: 4px; }
.era-grid-dow { text-align: center; font-size: 0.68rem; font-weight: 700;
color: var(--text2); padding: 4px 0; text-transform: uppercase;
letter-spacing: 0.05em; }
.era-grid-cell {
aspect-ratio: 1;
border-radius: 8px;
display: flex; flex-direction: column;
align-items: center; justify-content: center;
font-size: 0.78rem; font-weight: 600;
cursor: pointer;
position: relative;
border: 1.5px solid transparent;
transition: all 0.15s;
overflow: hidden;
min-height: 38px;
}
.era-grid-cell:hover { border-color: var(--accent); z-index: 1; transform:
scale(1.08); }
.[Link] { border-color: var(--accent) !important;
box-shadow: 0 0 0 2px var(--accent-light); }
.[Link] { cursor: default; background: transparent
!important; }
.[Link]:hover { transform: none; border-color: transparent;
}
.era-grid-day-num { font-size: 0.82rem; font-weight: 700; line-height: 1;
z-index: 1; }
.era-grid-dot { width: 5px; height: 5px; border-radius: 50%; background:
white; margin-top: 3px; z-index: 1; opacity: 0.9; }
.era-grid-era-bar { position: absolute; bottom: 0; left: 0; right: 0; height:
4px; }
.era-grid-month-title { font-family: var(--font-display); font-size: 1rem;
font-weight: 700; color: var(--text); margin-bottom: 8px; margin-top: 4px;
}
/* ---- BACKUP ---- */
/* ---- BOTTOM NAV (mobile only) ---- */
#bottom-nav {
display: none;
position: fixed; bottom: 0; left: 0; right: 0;
height: 64px;
background: var(--surface);
border-top: 1px solid var(--border);
z-index: 200;
flex-direction: row;
align-items: stretch;
box-shadow: 0 -2px 16px rgba(44,36,32,0.10);
}
.bnav-item {
flex: 1;
display: flex; flex-direction: column; align-items: center;
justify-content: center;
gap: 3px;
font-size: 0.62rem;
font-weight: 600;
color: var(--text2);
background: none; border: none;
cursor: pointer;
transition: color 0.18s;
padding: 6px 2px 2px;
letter-spacing: 0.03em;
}
.bnav-item .bicon { font-size: 1.3rem; line-height: 1; }
.[Link] { color: var(--accent); }
.[Link] .bicon { transform: scale(1.15); }
/* ---- ERA TIMELINE MOBILE FIX ---- */
.era-day-card { overflow: hidden; }
.era-day-entries { min-width: 0; overflow: hidden; }
.era-day-entry-title { max-width: 100%; }
.era-label-badge { max-width: 80px; overflow: hidden; text-overflow:
ellipsis; }
@media (max-width: 700px) {
:root { --sidebar-w: 0px; }
#sidebar { display: none !important; }
#bottom-nav { display: flex; }
#main {
margin-left: 0 !important;
padding-bottom: 64px;
}
.topbar {
padding: 12px 16px;
}
.topbar h2 { font-size: 1.1rem; }
.topbar-meta { font-size: 0.68rem; max-width: 150px; overflow:
hidden; text-overflow: ellipsis; white-space: nowrap; }
.page { padding: 14px 14px 10px; max-width: 100%; }
.entry-meta-row { grid-template-columns: 1fr 1fr; gap: 10px; }
.entry-meta-row-3 { grid-template-columns: 1fr; gap: 10px; }
.datetime-display, .timer-display { font-size: 0.72rem; padding: 8px
10px; }
.stats-grid { grid-template-columns: 1fr 1fr; gap: 10px; }
.stat-card { padding: 12px 10px; }
.stat-value { font-size: 1.5rem; }
.modal { padding: 20px 16px; border-radius: 14px; }
.modal-overlay { padding: 12px; }
.filter-row { flex-direction: column; gap: 8px; }
.filter-row select, .filter-row input { max-width: 100%; }
.entry-card { padding: 14px 14px; }
.entry-card-actions { flex-wrap: wrap; gap: 6px; }
.btn { padding: 8px 14px; font-size: 0.82rem; }
.btn-sm { padding: 6px 10px; font-size: 0.75rem; }
.backup-options { flex-direction: column; }
.backup-options .btn { width: 100%; justify-content: center; }
.settings-section { padding: 16px 14px; }
.add-inline-wrap { flex-direction: column; }
.add-inline-wrap .btn { width: 100%; justify-content: center; }
.section-header { flex-direction: column; align-items: flex-start; gap:
10px; }
.bar-label { width: 70px; font-size: 0.72rem; }
.cq-list-item { flex-direction: column; align-items: flex-start; gap: 6px; }
.detail-content { padding: 16px; font-size: 0.88rem; }
/* ERA TIMELINE MOBILE */
.era-timeline { padding-left: 18px; }
.era-day-card { padding: 8px 8px; gap: 6px; }
.era-day-date { width: 28px; font-size: 0.68rem; }
.era-label-badge { display: none; }
.era-day-era-band { width: 4px; }
.era-day-entry-title { font-size: 0.78rem; }
.era-nav-controls { gap: 6px; }
.era-legend-chip { font-size: 0.7rem; padding: 3px 7px; }
}
@media (max-width: 380px) {
.entry-meta-row { grid-template-columns: 1fr; }
.topbar-meta { display: none; }
}
</style>
</head>
<body>
<!-- SIDEBAR -->
<nav id="sidebar">
<div class="sidebar-logo">
<h1>Journal</h1>
<p>Personal Diary</p>
</div>
<div class="sidebar-nav">
<button class="nav-item active" onclick="showPage('new-entry')">
<span class="icon">'þ</span><span>New Entry</span>
</button>
<button class="nav-item" onclick="showPage('entries')">
<span class="icon">Ø=ÜÖ</span><span>All Entries</span>
</button>
<button class="nav-item" onclick="showPage('favourites')">
<span class="icon">+P</span><span>Favourites</span>
</button>
<button class="nav-item" onclick="showPage('eras')">
<span class="icon">Ø=ÝÓþ</span><span>Eras</span>
</button>
<button class="nav-item" onclick="showPage('search')">
<span class="icon">Ø=Ý</span><span>Search</span>
</button>
<button class="nav-item" onclick="showPage('stats')">
<span class="icon">Ø=ÜÊ</span><span>Statistics</span>
</button>
<button class="nav-item" onclick="showPage('braindump')">
<span class="icon">Ø>Ýà</span> Thought Capture
</button>
<button class="nav-item" onclick="showPage('other')">
<span class="icon">Ø=ÝÂþ</span><span>Other</span>
</button>
<button class="nav-item" onclick="showPage('backup')">
<span class="icon">Ø=ܾ</span><span>Backup</span>
</button>
<button class="nav-item" onclick="showPage('settings')">
<span class="icon">&™þ</span><span>Settings</span>
</button>
</div>
<div class="sidebar-footer">
<button class="theme-toggle" onclick="toggleTheme()">
<span>Ø<ß Dark Mode</span>
<div class="toggle-pill" id="dark-toggle"><div
class="toggle-knob"></div></div>
</button>
</div>
</nav>
<!-- BOTTOM NAV (mobile) -->
<nav id="bottom-nav">
<button class="bnav-item active" onclick="showPage('new-entry')"
id="bnav-new-entry"><span
class="bicon">'þ</span><span>Write</span></button>
<button class="bnav-item" onclick="showPage('entries')"
id="bnav-entries"><span
class="bicon">Ø=ÜÖ</span><span>Entries</span></button>
<button class="bnav-item" onclick="showPage('favourites')"
id="bnav-favourites"><span
class="bicon">+P</span><span>Stars</span></button>
<button class="bnav-item" onclick="showPage('eras')"
id="bnav-eras"><span
class="bicon">Ø=ÝÓþ</span><span>Eras</span></button>
<button class="bnav-item" onclick="showPage('search')"
id="bnav-search"><span
class="bicon">Ø=Ý</span><span>Search</span></button>
<button class="bnav-item" onclick="showPage('stats')"
id="bnav-stats"><span
class="bicon">Ø=ÜÊ</span><span>Stats</span></button>
<button class="bnav-item" onclick="showPage('braindump')"
id="bnav-braindump"><span
class="bicon">Ø>Ýà</span><span>Capture</span></button>
<button class="bnav-item" onclick="showPage('other')"
id="bnav-other"><span
class="bicon">Ø=ÝÂþ</span><span>Other</span></button>
<button class="bnav-item" onclick="showPage('backup')"
id="bnav-backup"><span
class="bicon">Ø=ܾ</span><span>Backup</span></button>
<button class="bnav-item" onclick="showPage('settings')"
id="bnav-settings"><span
class="bicon">&™þ</span><span>Settings</span></button>
</nav>
<!-- MAIN -->
<div id="main">
<div class="topbar">
<h2 id="page-title">New Entry</h2>
<span class="topbar-meta" id="live-clock"></span>
</div>
<!-- ======= NEW ENTRY PAGE ======= -->
<div class="page active" id="page-new-entry">
<!-- Date/Time & Total Timer row -->
<div class="entry-meta-row" style="margin-bottom:18px;">
<div>
<label class="form-label">Ø=ÜÅ Date & Time</label>
<div class="datetime-display" id="entry-datetime">—</div>
</div>
<div>
<label class="form-label">#ñ Total Time (all questions)</label>
<div class="timer-display" id="writing-timer">#ñ 00:00</div>
</div>
</div>
<!-- Meta: mood, category, tags -->
<div class="entry-meta-row-3">
<div>
<label class="form-label">Ø=Þ Mood</label>
<div class="mood-grid" id="mood-grid"></div>
</div>
<div>
<label class="form-label">Ø=ÝÂ Category</label>
<select id="entry-category"
onchange="renderCustomQuestionsForm()">
<option value="">Select category…</option>
</select>
<button class="btn btn-ghost btn-sm" style="margin-top:6px;"
onclick="promptAddCategory()">+ Add new</button>
</div>
<div>
<label class="form-label">Ø<ß÷ Tags <span
style="font-weight:400;text-transform:none;letter-spacing:0">(press
Enter)</span></label>
<div class="tags-input-wrap" id="tags-wrap">
<input class="tags-input" id="tag-input" placeholder="Add tag…"
onkeydown="handleTagInput(event)">
</div>
</div>
</div>
<!-- Custom Questions with per-question timer -->
<div id="cq-section">
<div id="cq-answers"></div>
</div>
<!-- No questions fallback -->
<div id="cq-empty" style="display:none;text-align:center;padding:32px
16px;background:var(--surface);border:1px solid
var(--border);border-radius:var(--radius);margin-bottom:18px;">
<div style="font-size:2rem;margin-bottom:8px;">Ø=ܬ</div>
<p style="font-size:0.88rem;color:var(--text2);">Koi custom question
set nahi hai.</p>
<button class="btn btn-outline btn-sm" style="margin-top:12px;"
onclick="showPage('settings')">&™ Settings mein add karein</button>
</div>
<!-- Title -->
<div class="form-group">
<label class="form-label">Ø=ÜÌ Title <span
style="color:var(--danger);font-size:0.85em;">*</span></label>
<input type="text" id="entry-title" placeholder="Is entry ka title likhein
(zaruri hai)…" required>
</div>
<!-- hidden textarea kept for save logic compatibility -->
<textarea id="entry-text" style="display:none;"></textarea>
<div style="display:flex;align-items:center;gap:10px;flex-wrap:wrap;">
<button class="btn btn-primary" onclick="saveEntry()">Ø=ܾ Save
Entry</button>
<button class="btn btn-outline" onclick="clearEntry()">'
Clear</button>
<span id="draft-status"
style="font-size:0.75rem;color:var(--text2);font-style:italic;"></span>
</div>
</div>
<!-- ======= ALL ENTRIES PAGE ======= -->
<div class="page" id="page-entries">
<div class="section-header">
<h2>All Entries</h2>
<button class="btn btn-outline" onclick="showPage('new-entry')">+
New Entry</button>
</div>
<div id="entries-list-container"></div>
</div>
<!-- ======= QUESTIONS PAGE ======= -->
<!-- ======= FAVOURITES PAGE ======= -->
<div class="page" id="page-favourites">
<div class="section-header"><h2>+P Favourites</h2></div>
<div id="favourites-list" class="entries-list"></div>
</div>
<!-- ======= ERAS PAGE ======= -->
<div class="page" id="page-eras">
<!-- Header row -->
<div style="display:flex;align-items:center;justify-content:space-betwe
en;margin-bottom:16px;flex-wrap:wrap;gap:10px;">
<h2 style="font-family:var(--font-display);font-size:1.5rem;">Ø=ÝÓþ
Eras</h2>
<div style="display:flex;gap:8px;flex-wrap:wrap;">
<button class="btn btn-outline btn-sm" onclick="exportEras()"
title="Eras export karein">+ Export</button>
<label class="btn btn-outline btn-sm" style="cursor:pointer;"
title="Eras import karein">+ Import<input type="file" accept=".json"
style="display:none;" onchange="importEras(event)"></label>
<button class="btn btn-primary btn-sm"
onclick="toggleEraForm()">+ New Era</button>
</div>
</div>
<!-- Era Add Form -->
<div class="era-add-form" id="era-add-form"
style="display:none;margin-bottom:20px;">
<div style="font-weight:700;font-size:0.9rem;color:var(--text);margin-
bottom:4px;">New Era</div>
<div class="era-add-form-row">
<input type="text" id="era-name-input" placeholder="Era name
(e.g. Termux Era)">
</div>
<div class="era-add-form-row">
<input type="date" id="era-start-input">
<span
style="color:var(--text2);font-size:0.82rem;align-self:center;">to</span>
<input type="date" id="era-end-input" placeholder="End
(optional)">
</div>
<div>
<div style="font-size:0.75rem;color:var(--text2);margin-bottom:6px;f
ont-weight:600;text-transform:uppercase;letter-spacing:0.06em;">Color<
/div>
<div class="era-color-picker" id="era-color-picker"></div>
<div
style="display:flex;align-items:center;gap:8px;margin-top:8px;">
<label style="font-size:0.75rem;color:var(--text2);font-weight:600;"
>Custom:</label>
<input type="color" id="era-custom-color" value="#c17f4a"
style="width:36px;height:28px;padding:0 2px;border:1.5px solid var(--bo
rder);border-radius:6px;cursor:pointer;background:var(--surface);"
oninput="setEraCustomColor([Link])">
<button class="btn btn-outline btn-sm"
onclick="addEraCustomColor()" style="font-size:0.75rem;">+ Add to
palette</button>
</div>
</div>
<div style="display:flex;gap:8px;">
<button class="btn btn-primary btn-sm" onclick="saveEra()">Save
Era</button>
<button class="btn btn-outline btn-sm"
onclick="toggleEraForm()">Cancel</button>
</div>
</div>
<!-- Era Legend -->
<div class="era-nav-legend" id="era-legend"></div>
<!-- View toggle + Star filter -->
<div style="display:flex;align-items:center;justify-content:space-betwe
en;flex-wrap:wrap;gap:8px;margin-bottom:14px;">
<div class="era-star-filter" style="margin-bottom:0;">
<button id="era-filter-all" class="active"
onclick="setEraStarFilter('all')">All entries</button>
<button id="era-filter-starred" onclick="setEraStarFilter('starred')">+P
Starred only</button>
</div>
<div style="display:flex;gap:6px;">
<button id="era-view-timeline-btn" class="btn btn-outline btn-sm
active-view-btn" onclick="setEraView('timeline')">&0 Timeline</button>
<button id="era-view-grid-btn" class="btn btn-outline btn-sm"
onclick="setEraView('grid')">"ž Grid</button>
</div>
</div>
<!-- Month nav -->
<div class="era-nav-controls">
<button class="btn btn-outline btn-sm"
onclick="eraMonthNav(-1)">%À</button>
<div class="era-nav-month-label" id="era-month-label">—</div>
<button class="btn btn-outline btn-sm"
onclick="eraMonthNav(1)">%¶</button>
<button class="btn btn-ghost btn-sm"
onclick="eraGoToday()">Today</button>
</div>
<!-- Timeline view -->
<div class="era-timeline" id="era-timeline-container"></div>
<!-- Grid view -->
<div id="era-grid-container" style="display:none;"></div>
</div>
<!-- ======= SEARCH PAGE ======= -->
<div class="page" id="page-search">
<div class="section-header"><h2>Search Entries</h2></div>
<div class="search-bar-wrap">
<span class="search-icon">Ø=Ý</span>
<input type="text" id="search-q" placeholder="Search by keyword,
title, tag…" oninput="doSearch()">
</div>
<div class="filter-row">
<select id="filter-mood" onchange="doSearch()"><option
value="">All Moods</option></select>
<select id="filter-cat" onchange="doSearch()"><option value="">All
Categories</option></select>
<input type="date" id="filter-date-from" onchange="doSearch()"
title="From date">
<input type="date" id="filter-date-to" onchange="doSearch()"
title="To date">
<button class="btn btn-outline btn-sm"
onclick="clearSearch()">Clear Filters</button>
</div>
<div id="search-results"></div>
</div>
<!-- ======= STATS PAGE ======= -->
<div class="page" id="page-stats">
<div class="section-header"><h2>Statistics</h2></div>
<div class="stats-grid" id="stats-grid"></div>
<div class="chart-section">
<h3>Mood Distribution</h3>
<div class="bar-chart" id="mood-chart"></div>
</div>
<div class="chart-section">
<h3>Top Categories</h3>
<div class="bar-chart" id="cat-chart"></div>
</div>
<div class="chart-section">
<h3>Writing Activity (Last 7 days)</h3>
<div class="bar-chart" id="activity-chart"></div>
</div>
<!-- Thought Capture Stats -->
<div class="chart-section" style="border:1.5px solid
var(--accent);background:var(--accent-light);">
<h3 style="color:var(--accent2);">Ø>Ýà Thought Capture
Overview</h3>
<div class="stats-grid" id="bd-stats-grid"
style="margin-bottom:16px;"></div>
<div class="bar-chart" id="bd-cat-chart"></div>
</div>
</div>
<!-- ======= BACKUP PAGE ======= -->
<div class="page" id="page-braindump">
<div class="section-header">
<h2>Ø>Ýà Thought Capture</h2>
<p
style="font-size:0.85rem;color:var(--text2);margin-top:4px;">Capture any
idea or task that crosses your mind — log it here and revisit it later. Stay
focused, stay productive.</p>
</div>
<!-- Add Item -->
<div style="background:var(--surface);border-radius:var(--radius);pad
ding:20px;margin-bottom:16px;box-shadow:var(--shadow);">
<div
style="display:flex;gap:10px;align-items:flex-start;flex-wrap:wrap;">
<div style="flex:1;min-width:200px;">
<textarea id="bd-input" placeholder="What's on your mind? Jot
down any idea, task, or thought — you can write multiple lines here."
style="margin-bottom:8px;min-height:80px;resize:vertical;"></textarea>
<div style="display:flex;gap:8px;flex-wrap:wrap;">
<select id="bd-category" style="width:auto;flex:1;"></select>
<select id="bd-priority" style="width:auto;">
<option value="low">Ø=ßâ Low</option>
<option value="medium">Ø=ßá Medium</option>
<option value="high">Ø=Ý4 High</option>
</select>
</div>
</div>
<button class="btn btn-primary" onclick="addBrainDump()"
style="align-self:flex-start;">+ Add</button>
</div>
</div>
<!-- Custom Categories Manager -->
<details style="background:var(--surface);border-radius:var(--radius);
padding:14px 18px;margin-bottom:16px;box-shadow:var(--shadow);">
<summary style="cursor:pointer;font-weight:600;font-size:0.88rem;c
olor:var(--text2);user-select:none;">Ø=ÝÂ Manage Categories (Add /
Delete)</summary>
<div style="margin-top:14px;">
<div id="bd-cat-list"
style="display:flex;flex-wrap:wrap;gap:8px;margin-bottom:12px;"></div>
<div style="display:flex;gap:8px;">
<input type="text" id="bd-new-cat-input" placeholder="Emoji +
name, e.g. Ø<ß® Gaming" style="flex:1;">
<button class="btn btn-primary btn-sm"
onclick="addBDCategory()">+ Add</button>
</div>
<p style="font-size:0.75rem;color:var(--text2);margin-top:8px;">Ø=Ü¡
Start with an emoji followed by the category name — e.g. "Ø<߬ Movies" or
"Ø=ܪ Fitness"</p>
</div>
</details>
<!-- Filter Tabs -->
<div
style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px;">
<button class="btn btn-outline btn-sm bd-filter-btn active-filter"
onclick="filterBD('all')" id="bdf-all">All</button>
<button class="btn btn-outline btn-sm bd-filter-btn"
onclick="filterBD('pending')" id="bdf-pending">#ó Pending</button>
<button class="btn btn-outline btn-sm bd-filter-btn"
onclick="filterBD('done')" id="bdf-done">' Done</button>
</div>
<!-- Stats bar -->
<div id="bd-stats" style="font-size:0.8rem;color:var(--text2);margin-bot
tom:14px;font-family:var(--font-mono);"></div>
<!-- List -->
<div id="bd-list"></div>
<!-- Completed Analysis -->
<div id="bd-analysis" style="display:none;margin-top:24px;">
<div class="chart-section" style="border:1.5px solid
var(--success);background:rgba(39,174,96,0.06);">
<h3 style="color:var(--success);">' Completed Tasks —
Breakdown</h3>
<div id="bd-analysis-content"></div>
</div>
</div>
</div>
<div class="page" id="page-backup">
<div class="section-header"><h2>Backup & Restore</h2></div>
<!-- Last Backup Info -->
<div id="last-backup-bar"
style="background:var(--surface2);border:1px solid
var(--border);border-radius:10px;padding:10px 16px;margin-bottom:16p
x;display:flex;flex-wrap:wrap;gap:10px;align-items:center;font-size:0.82r
em;color:var(--text2);">
<span>Ø=ÜÅ <b>Last Journal Backup:</b> <span
id="last-backup-journal">Never</span></span>
<span style="color:var(--border);">|</span>
<span>Ø=ÝÓþ <b>Last Eras Backup:</b> <span
id="last-backup-eras">Never</span></span>
<span style="color:var(--border);">|</span>
<span>&™þ <b>Last Settings Backup:</b> <span
id="last-backup-settings">Never</span></span>
<span style="color:var(--border);">|</span>
<span>Ø>Ýà <b>Last Capture Backup:</b> <span
id="last-backup-capture">Never</span></span>
</div>
<!-- Google Drive Upload Section -->
<div class="backup-section" style="border:2px solid
var(--accent);background:var(--accent-light);">
<h3 style="color:var(--accent2);">&þ Google Drive Upload</h3>
<p
style="font-size:0.82rem;color:var(--text2);margin-bottom:14px;">Flask
server + rclone se seedha Google Drive par upload karo</p>
<div
style="margin-bottom:12px;display:flex;flex-wrap:wrap;gap:6px;">
<span id="flask-status" class="flask-status-badge">#ó
Checking...</span>
<span id="rclone-status" class="flask-status-badge"
style="margin-left:0px;"></span>
</div>
<div class="backup-options" style="margin-bottom:14px;">
<button class="btn btn-primary" onclick="uploadAllToDrive()">&þ
Sab Upload Karo</button>
<button class="btn btn-outline"
onclick="uploadSelectedToDrive()">&þ Selected Upload Karo</button>
<button class="btn btn-outline" onclick="refreshDriveStatus()">Ø=Ý
Drive Status Check</button>
<button class="btn btn-outline" onclick="updateFlaskStatus()">Ø=Ý
Server Check</button>
</div>
<div id="upload-progress-wrap"
style="display:none;margin-bottom:14px;">
<div style="display:flex;justify-content:space-between;align-items:c
enter;margin-bottom:6px;">
<span
style="font-size:0.82rem;font-weight:600;color:var(--accent);"
id="upload-progress-label">Uploading...</span>
<span
style="font-size:0.78rem;color:var(--text2);font-family:var(--font-mono);"
id="upload-progress-count">0 / 0</span>
</div>
<div style="background:var(--border);border-radius:20px;height:12p
x;overflow:hidden;">
<div id="upload-progress-bar" style="height:100%;background:lin
ear-gradient(90deg,var(--accent),var(--accent2));border-radius:20px;widt
h:0%;transition:width 0.3s;"></div>
</div>
<div id="upload-current-entry" style="font-size:0.75rem;color:var(--t
ext2);margin-top:6px;font-style:italic;"></div>
</div>
<div id="upload-result-summary" style="display:none;padding:10px
14px;border-radius:10px;background:var(--surface);border:1px solid
var(--border);margin-bottom:12px;font-size:0.82rem;"></div>
<p style="font-size:0.75rem;color:var(--text2);">Ø=Ü¡ Har nai entry
automatically local save + Drive sync hoti hai</p>
</div>
<!-- Drive Status List -->
<div class="backup-section" id="drive-status-section"
style="display:none;">
<h3>Ø=ÜÊ Drive Upload Status</h3>
<p
style="font-size:0.82rem;color:var(--text2);margin-bottom:12px;">Kaunsi
entries Google Drive pe hain</p>
<div
style="display:flex;gap:8px;margin-bottom:12px;flex-wrap:wrap;">
<span id="drive-stat-uploaded"
style="font-size:0.82rem;padding:4px 12px;background:#e8f5e9;color:#
2e7d32;border-radius:20px;font-weight:600;">' 0 Uploaded</span>
<span id="drive-stat-pending" style="font-size:0.82rem;padding:4px
12px;background:#fff3e0;color:#e65100;border-radius:20px;font-weight:
600;">#ó 0 Pending</span>
</div>
<div id="drive-entries-list"
style="display:flex;flex-direction:column;gap:8px;"></div>
</div>
<!-- Export Journal Entries -->
<div class="backup-section">
<h3>Ø=ÜÔ Journal Entries Backup</h3>
<p style="font-size:0.85rem;color:var(--text2);margin-bottom:14px;">
Download journal entries by date range</p>
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;m
argin-bottom:14px;padding:12px;background:var(--surface2);border-radi
us:10px;border:1px solid var(--border);">
<div style="display:flex;flex-direction:column;gap:4px;flex:1;min-wid
th:120px;">
<label style="font-size:0.72rem;font-weight:600;color:var(--text2);t
ext-transform:uppercase;letter-spacing:0.06em;">From</label>
<input type="date" id="backup-date-from"
style="font-size:0.82rem;padding:7px 10px;">
</div>
<div style="display:flex;flex-direction:column;gap:4px;flex:1;min-wid
th:120px;">
<label style="font-size:0.72rem;font-weight:600;color:var(--text2);t
ext-transform:uppercase;letter-spacing:0.06em;">To</label>
<input type="date" id="backup-date-to"
style="font-size:0.82rem;padding:7px 10px;">
</div>
<div style="display:flex;flex-direction:column;gap:4px;">
<label style="font-size:0.72rem;font-weight:600;color:var(--text2);t
ext-transform:uppercase;letter-spacing:0.06em;">Quick</label>
<select id="backup-quick-range"
onchange="applyBackupQuickRange()"
style="font-size:0.82rem;padding:7px 10px;">
<option value="">Custom</option>
<option value="7">Last 7 days</option>
<option value="30">Last 30 days</option>
<option value="90">Last 3 months</option>
<option value="180">Last 6 months</option>
<option value="365">Last 1 year</option>
<option value="all">All time</option>
</select>
</div>
<button class="btn btn-outline btn-sm"
onclick="applyBackupDateFilter()" style="align-self:flex-end;">Ø=Ý
Filter</button>
<button class="btn btn-ghost btn-sm" onclick="clearBackupFilter()"
style="align-self:flex-end;">' Clear</button>
</div>
<div id="backup-filter-info" style="font-size:0.8rem;color:var(--text2);
margin-bottom:10px;font-family:var(--font-mono);"></div>
<div class="backup-options">
<button class="btn btn-primary" onclick="exportAll('json')">+ Export
All (JSON)</button>
<button class="btn btn-outline" onclick="exportFiltered('json')">+
Export Filtered</button>
<button class="btn btn-outline"
onclick="exportSelected('json')">Export Selected (JSON)</button>
<button class="btn btn-outline" onclick="exportZip()">Ø=ÝÜ Export
ZIP</button>
</div>
</div>
<!-- Eras Backup -->
<div class="backup-section">
<h3>Ø=ÝÓþ Eras Backup</h3>
<p
style="font-size:0.85rem;color:var(--text2);margin-bottom:14px;">Export
or import your Era definitions</p>
<div class="backup-options">
<button class="btn btn-primary" onclick="exportErasBackup()">+
Export Eras (JSON)</button>
<label class="btn btn-outline" style="cursor:pointer;">+ Import Eras
<input type="file" accept=".json" style="display:none"
onchange="importErasBackup(event)"></label>
</div>
</div>
<!-- Settings Backup -->
<div class="backup-section">
<h3>&™þ Settings Backup</h3>
<p
style="font-size:0.85rem;color:var(--text2);margin-bottom:14px;">Backup
all settings — moods, categories, custom questions, appearance</p>
<div class="backup-options">
<button class="btn btn-primary"
onclick="exportSettingsBackup()">+ Export Settings (JSON)</button>
<label class="btn btn-outline" style="cursor:pointer;">+ Import
Settings <input type="file" accept=".json" style="display:none"
onchange="importSettingsBackup(event)"></label>
</div>
</div>
<!-- Thought Capture Backup -->
<div class="backup-section">
<h3>Ø>Ýà Thought Capture Backup</h3>
<p
style="font-size:0.85rem;color:var(--text2);margin-bottom:14px;">Backup
all captured thoughts and custom categories</p>
<div class="backup-options">
<button class="btn btn-primary"
onclick="exportCaptureBackup()">+ Export Capture (JSON)</button>
<label class="btn btn-outline" style="cursor:pointer;">+ Import
Capture <input type="file" accept=".json" style="display:none"
onchange="importCaptureBackup(event)"></label>
</div>
</div>
<!-- Import -->
<div class="backup-section">
<h3>Ø=Üå Import Journal Entries</h3>
<div class="backup-options">
<label class="btn btn-outline" style="cursor:pointer;">Ø=ÜÂ Import
JSON (Multiple) <input type="file" accept=".json" style="display:none"
multiple onchange="importFile(event,'json')"></label>
</div>
<p style="font-size:0.78rem;color:var(--text2);margin-top:8px;">Ø=Ü¡
Ctrl+Click or Shift+Click to select multiple files at once!</p>
</div>
<!-- Select Entries -->
<div class="backup-section">
<h3>Ø=ÜË Select Entries</h3>
<div
style="display:flex;gap:8px;margin-bottom:12px;flex-wrap:wrap;">
<button class="btn btn-outline btn-sm"
onclick="selectAllBackup()">Select All</button>
<button class="btn btn-outline btn-sm"
onclick="deselectAllBackup()">Deselect All</button>
<span id="backup-selected-count"
style="font-size:0.82rem;color:var(--text2);align-self:center;"></span>
</div>
<div id="backup-entries-list"></div>
</div>
</div>
<!-- ======= SETTINGS PAGE ======= -->
<div class="page" id="page-settings">
<div class="section-header"><h2>Settings</h2></div>
<div class="settings-section">
<h3>Ø<ߨ Appearance</h3>
<div class="setting-row">
<div class="setting-info"><h4>Dark Theme</h4><p>Save your
theme preference</p></div>
<button class="btn btn-outline btn-sm"
onclick="toggleTheme()">Toggle</button>
</div>
<div class="setting-row">
<div class="setting-info"><h4>Font Size</h4><p>Entry text
size</p></div>
<select id="font-size-sel" onchange="changeFontSize()"
style="width:130px">
<option value="0.9rem">Normal</option>
<option value="1.05rem">Large</option>
<option value="1.18rem">X-Large</option>
</select>
</div>
</div>
<div class="settings-section">
<h3>Ø=ܬ Custom Questions</h3>
<p
style="font-size:0.82rem;color:var(--text2);margin-bottom:12px;">These
questions appear in each new entry</p>
<div class="cq-list" id="settings-cq-list"></div>
<div class="add-inline-wrap" style="margin-top:12px;">
<input type="text" id="new-cq-input" placeholder="Type a
question…">
<button class="btn btn-primary btn-sm"
onclick="addCustomQuestion()">+ Add</button>
</div>
</div>
<div class="settings-section" id="settings-catq-section">
<h3>Ø=ÜÂ Category-wise Questions</h3>
<p
style="font-size:0.82rem;color:var(--text2);margin-bottom:12px;">Kisi
specific category ke liye extra questions add karo — sirf woh category
select karne par dikhenge</p>
<div style="display:flex;gap:8px;align-items:center;margin-bottom:14
px;flex-wrap:wrap;">
<select id="catq-category-sel" onchange="renderCategoryQList()"
style="flex:1;min-width:140px;">
<option value="">-- Category chunein --</option>
</select>
<span id="catq-count-badge" style="font-size:0.75rem;color:var(--a
ccent);font-weight:600;white-space:nowrap;"></span>
</div>
<div id="catq-list" style="margin-bottom:10px;"></div>
<div class="add-inline-wrap" id="catq-add-wrap"
style="margin-top:8px;display:none;">
<input type="text" id="new-catq-input" placeholder="Category ke
liye question likhein…">
<button class="btn btn-primary btn-sm"
onclick="addCategoryQuestion()">+ Add</button>
</div>
</div>
<div class="settings-section">
<h3>Ø=ÝÂ Custom Categories</h3>
<div class="cat-list" id="settings-cat-list"></div>
<div class="add-inline-wrap" style="margin-top:12px;">
<input type="text" id="new-cat-input" placeholder="New
category…">
<button class="btn btn-primary btn-sm" onclick="addCategory()">+
Add</button>
</div>
</div>
<div class="settings-section">
<h3>Ø=Þ Mood Options</h3>
<div class="mood-grid" id="settings-mood-preview"></div>
<div class="add-inline-wrap" style="margin-top:12px;">
<input type="text" id="new-mood-input" placeholder="New mood
emoji + label e.g. Ø>Ý) Amazing">
<button class="btn btn-primary btn-sm" onclick="addMood()">+
Add</button>
</div>
</div>
<div class="settings-section">
<h3>Ø=ÜÁ Local Save + rclone Config</h3>
<p
style="font-size:0.82rem;color:var(--text2);margin-bottom:14px;">Har nai
entry automatically local save hogi aur rclone se Google Drive par sync
hogi</p>
<div style="margin-bottom:12px;">
<span id="flask-status" class="flask-status-badge">#ó Server check
ho raha hai...</span>
<span id="rclone-status" class="flask-status-badge"
style="margin-left:8px;"></span>
</div>
<div class="form-group">
<label class="form-label">Ø=ÜÂ Save Path (Local)</label>
<input type="text" id="flask-journal-dir"
placeholder="/storage/emulated/0/Journal">
<p
style="font-size:0.73rem;color:var(--text2);margin-top:4px;">Entries
yahan save hongi</p>
</div>
<div class="form-group">
<label class="form-label">&þ rclone Remote Path</label>
<input type="text" id="flask-rclone-remote"
placeholder="gdrive:Journal">
<p
style="font-size:0.73rem;color:var(--text2);margin-top:4px;">Format:
remote_name:folder (e.g. gdrive:MyJournal)</p>
</div>
<div class="setting-row" style="margin-bottom:12px;">
<div class="setting-info"><h4>Auto rclone Sync</h4><p>Har entry
save pe Drive par sync</p></div>
<label
style="display:flex;align-items:center;gap:8px;cursor:pointer;">
<input type="checkbox" id="flask-rclone-toggle"
style="width:auto;" checked>
<span
style="font-size:0.82rem;color:var(--text2);">Enable</span>
</label>
</div>
<div style="display:flex;gap:8px;flex-wrap:wrap;">
<button class="btn btn-primary btn-sm"
onclick="saveFlaskConfig()">Ø=ܾ Config Save Karo</button>
<button class="btn btn-outline btn-sm"
onclick="manualRcloneSync()">&þ Abhi Sync Karo</button>
<button class="btn btn-outline btn-sm"
onclick="updateFlaskStatus()">Ø=Ý Status Check</button>
</div>
<div style="margin-top:12px;background:var(--surface2);border-radiu
s:10px;padding:12px
14px;font-size:0.78rem;color:var(--text2);line-height:1.8;">
<b style="color:var(--accent);">rclone setup (ek baar):</b><br>
1. Termux: <code>pkg install rclone</code><br>
2. <code>rclone config</code> !’ n !’ name: gdrive !’ type: drive !’
follow steps<br>
3. Upar remote name daalo (e.g. gdrive:Journal) '
</div>
</div>
<div class="settings-section">
<h3>Ø=ÝÑ Data Management</h3>
<div class="setting-row">
<div class="setting-info"><h4>Clear All
Entries</h4><p>Permanently delete all journal entries</p></div>
<button class="btn btn-danger btn-sm"
onclick="clearAllData()">Clear All</button>
</div>
</div>
</div>
<!-- ======= OTHER PAGE ======= -->
<div class="page" id="page-other">
<div class="section-header">
<h2>Ø=ÝÂþ Other Apps</h2>
<p
style="font-size:0.85rem;color:var(--text2);margin-top:4px;">Custom
HTML tools — app icons ki tarah save karo aur ek tap mein kholo.</p>
</div>
<!-- Upload new HTML file -->
<div style="background:var(--surface);border-radius:var(--radius);pad
ding:20px;margin-bottom:18px;box-shadow:var(--shadow);border:1px
solid var(--border);">
<h3 style="font-family:var(--font-display);font-size:1rem;color:var(--te
xt);margin-bottom:14px;padding-bottom:10px;border-bottom:1px solid
var(--border);">'• Naya App Add Karo</h3>
<div style="display:flex;gap:10px;align-items:flex-end;flex-wrap:wrap
;margin-bottom:10px;">
<div style="flex:1;min-width:140px;">
<label class="form-label">App ka naam</label>
<input type="text" id="other-fname" placeholder="e.g. Budget,
Habit Tracker">
</div>
<div style="min-width:100px;">
<label class="form-label">Icon</label>
<div style="display:flex;gap:6px;align-items:center;">
<span id="other-icon-preview"
style="font-size:1.6rem;cursor:pointer;padding:4px
8px;background:var(--surface2);border-radius:8px;border:1.5px solid
var(--border);" onclick="toggleIconPicker()">Ø>Ýé</span>
<span style="font-size:0.75rem;color:var(--text2);">tap to
change</span>
</div>
</div>
<label class="btn btn-outline"
style="cursor:pointer;margin-bottom:0;white-space:nowrap;">
Ø=ÜÂ File Choose Karo
<input type="file" accept=".html,.htm" id="other-file-input"
style="display:none;" onchange="otherFileChosen(event)">
</label>
<button class="btn btn-primary" onclick="uploadOtherFile()"
style="white-space:nowrap;">Ø=ܾ Save Karo</button>
</div>
<!-- Icon picker -->
<div id="other-icon-picker" style="display:none;margin:10px 0;paddi
ng:12px;background:var(--surface2);border-radius:10px;border:1px solid
var(--border);">
<div style="font-size:0.75rem;font-weight:600;color:var(--text2);text
-transform:uppercase;letter-spacing:0.07em;margin-bottom:8px;">Icon
choose karo</div>
<div id="other-icon-grid"
style="display:flex;flex-wrap:wrap;gap:6px;"></div>
</div>
<div id="other-file-info"
style="font-size:0.78rem;color:var(--text2);min-height:18px;"></div>
</div>
<!-- App Grid -->
<div style="background:var(--surface);border-radius:var(--radius);pad
ding:20px;box-shadow:var(--shadow);border:1px solid var(--border);">
<div style="display:flex;align-items:center;justify-content:space-betw
een;margin-bottom:16px;padding-bottom:10px;border-bottom:1px solid
var(--border);">
<h3
style="font-family:var(--font-display);font-size:1rem;color:var(--text);">Ø=Üñ
My Apps</h3>
<button class="btn btn-outline btn-sm" onclick="loadOtherList()">Ø=Ý
Refresh</button>
</div>
<div id="other-files-list">
<div style="text-align:center;padding:30px;color:var(--text2);">
<div style="font-size:2rem;margin-bottom:8px;">Ø=Üí</div>
<div style="font-size:0.85rem;">Koi app nahi. Upar se add
karo!</div>
</div>
</div>
</div>
</div>
</div>
<!-- ======= MODAL: Entry Detail ======= -->
<div class="modal-overlay" id="detail-modal">
<div class="modal">
<button class="modal-close"
onclick="closeModal('detail-modal')">'</button>
<div id="detail-modal-content"></div>
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:18px;paddi
ng-top:14px;border-top:1px solid var(--border);">
<button class="btn btn-primary" onclick="editCurrentEntry()">'
Edit</button>
<button class="btn btn-outline" id="detail-star-btn"
onclick="toggleStarCurrent()">& Favourite</button>
<button class="btn btn-outline" onclick="exportEntry('json')">+
JSON</button>
<button class="btn btn-danger" onclick="deleteCurrentEntry()">Ø=ÝÑ
Delete</button>
</div>
</div>
</div>
<!-- ======= MODAL: Edit Entry ======= -->
<div class="modal-overlay" id="edit-modal">
<div class="modal">
<button class="modal-close"
onclick="closeModal('edit-modal')">'</button>
<h2 class="modal-title">Edit Entry</h2>
<div class="form-group">
<label class="form-label">Title</label>
<input type="text" id="edit-title">
</div>
<div class="form-group">
<label class="form-label">Entry</label>
<textarea id="edit-text" style="min-height:200px;"></textarea>
</div>
<div class="form-group">
<label class="form-label">Mood</label>
<div class="mood-grid" id="edit-mood-grid"></div>
</div>
<div class="form-group">
<label class="form-label">Category</label>
<select id="edit-category"></select>
</div>
<div class="form-group">
<label class="form-label">Tags</label>
<div class="tags-input-wrap" id="edit-tags-wrap">
<input class="tags-input" id="edit-tag-input" placeholder="Add
tag…" onkeydown="handleEditTagInput(event)">
</div>
</div>
<button class="btn btn-primary" onclick="saveEdit()">Ø=ܾ Save
Changes</button>
</div>
</div>
<!-- ======= SAVE LOCALLY MODAL ======= -->
<div class="modal-overlay" id="save-local-modal">
<div class="modal" style="max-width:440px;">
<button class="modal-close"
onclick="closeModal('save-local-modal')">'</button>
<h2 class="modal-title">Ø=ܾ Save to Device</h2>
<p
style="font-size:0.85rem;color:var(--text2);margin-bottom:18px;">Entry
browser mein save ho gayi '<br>Ab device storage mein bhi save
karein?</p>
<div class="form-group">
<label class="form-label">Format</label>
<div style="display:flex;gap:10px;">
<label id="fmt-json" style="cursor:pointer;padding:12px
14px;border:2px solid var(--accent);border-radius:10px;text-align:center;
background:var(--accent-light);flex:1;">
<div style="font-size:1.4rem;">Ø=ÝÂ</div>
<div style="font-size:0.82rem;font-weight:700;color:var(--accent);
margin-top:4px;">JSON</div>
<div style="font-size:0.72rem;color:var(--text2);">.json</div>
</label>
</div>
<input type="hidden" id="local-save-format" value="json">
</div>
<div
style="background:var(--surface2);border-radius:10px;padding:12px 14p
x;margin-bottom:14px;font-size:0.82rem;color:var(--text2);line-height:1.6
;">
<b style="color:var(--accent);">!9þ Kaise kaam karta hai:</b><br>
"Save Karein" dabate hi aapka browser <b>Save As dialog</b>
kholega — aap khud koi bhi folder choose kar sakte ho (Downloads,
Documents, ya koi bhi jagah). Folder lock nahi hoga! '
</div>
<div style="display:flex;gap:10px;margin-top:4px;">
<button class="btn btn-primary" style="flex:1;"
onclick="confirmLocalSave()">Ø=ܾ Save Karein</button>
<button class="btn btn-outline" onclick="rcloneSyncEntry()"
title="rclone se Drive par sync karo">&þ Drive Sync</button>
<button class="btn btn-outline"
onclick="closeModal('save-local-modal')">Skip</button>
</div>
</div>
</div>
<div class="toast" id="toast"></div>
<script src="[Link]
s"></script>
<script>
// =====================================
// STATE & STORAGE
// =====================================
let state = {
entries: [],
settings: {
dark: false,
customQuestions: [],
categoryQuestions: {}, // {categoryName: [q1, q2, ...]}
categories:
['Personal','Work','Travel','Health','Goals','Gratitude','Dreams','Ideas'],
moods: ['Ø=Þ Happy','Ø=Þ Calm','Ø=Þ Sad','Ø=Þ$ Angry','Ø=Þ0 Anxious','Ø>
Tired','Ø=Þ Excited','Ø>Ý Thoughtful','Ø=Þ6 Neutral','Ø>Ýp Grateful'],
fontSize: '0.9rem',
eras: []
}
};
let currentPage = 'new-entry';
let currentDetailId = null;
let editTags = [];
let newEntryTags = [];
let timerInterval = null;
let timerSeconds = 0;
let timerStarted = false;
let pendingSaveEntry = null;
// =====================================
// FLASK API CONFIG
// =====================================
const FLASK_API = '[Link]
let flaskConfig = { journal_dir: '/storage/emulated/0/Journal',
rclone_remote: 'gdrive:Journal', rclone_enabled: true };
async function loadFlaskConfig() {
try {
const r = await fetch(`${FLASK_API}/config`);
if ([Link]) flaskConfig = await [Link]();
renderFlaskConfigUI();
} catch {}
}
function renderFlaskConfigUI() {
const dirEl = [Link]('flask-journal-dir');
const remoteEl = [Link]('flask-rclone-remote');
const toggleEl = [Link]('flask-rclone-toggle');
if (dirEl) [Link] = flaskConfig.journal_dir || '';
if (remoteEl) [Link] = flaskConfig.rclone_remote || '';
if (toggleEl) [Link] = flaskConfig.rclone_enabled;
// Status update
updateFlaskStatus();
}
async function updateFlaskStatus() {
const statusEl = [Link]('flask-status');
const rcloneStatusEl = [Link]('rclone-status');
if (!statusEl) return;
try {
const r = await fetch(`${FLASK_API}/check_rclone`);
const d = await [Link]();
if (rcloneStatusEl) {
if ([Link]) {
[Link] = `' rclone available · Remotes:
${[Link](', ') || 'none'}`;
[Link] = 'flask-status-badge ok';
} else {
[Link] = ''L rclone nahi mila — pkg install
rclone';
[Link] = 'flask-status-badge err';
}
}
[Link] = '' Server connected';
[Link] = 'flask-status-badge ok';
} catch {
if (statusEl) { [Link] = ''L Server nahi mila — python
journal_server.py chalao'; [Link] = 'flask-status-badge err';
}
}
}
async function saveFlaskConfig() {
const dir = [Link]('flask-journal-dir')?.[Link]();
const remote =
[Link]('flask-rclone-remote')?.[Link]();
const enabled =
[Link]('flask-rclone-toggle')?.checked;
try {
const r = await fetch(`${FLASK_API}/config`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: [Link]({ journal_dir: dir, rclone_remote: remote,
rclone_enabled: enabled })
});
const d = await [Link]();
flaskConfig = { journal_dir: d.journal_dir, rclone_remote:
d.rclone_remote, rclone_enabled: enabled };
showToast('' Config save ho gaya!');
} catch {
showToast(''L Server se connect nahi hua!');
}
}
async function saveEntryToFlask(entry, fmt = 'txt', autoSync = true) {
try {
const r = await fetch(`${FLASK_API}/save`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: [Link]({ entry, fmt, auto_sync: autoSync })
});
const d = await [Link]();
if ([Link] === 'ok') {
let msg = `' Local save: ${[Link]}`;
if ([Link]?.status === 'success') msg += ' · &þ Drive sync!';
else if ([Link]?.status === 'error') msg += ` · & þ rclone:
${[Link]}`;
showToast(msg);
return d;
} else {
showToast('& þ Save error: ' + ([Link] || 'unknown'));
}
} catch {
showToast('& þ Flask server nahi mila — sirf LocalStorage mein save
hua');
}
return null;
}
async function manualRcloneSync(filename) {
showToast('#ó rclone sync ho raha hai...');
try {
const body = filename ? { filename } : {};
const r = await fetch(`${FLASK_API}/sync`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: [Link](body)
});
const d = await [Link]();
if ([Link] === 'success') showToast('&þ ' + [Link]);
else showToast(''L ' + [Link]);
} catch {
showToast(''L Server se connect nahi hua!');
}
}
async function flaskSaveAll() {
if (![Link]) { showToast('& þ Koi entry nahi!'); return; }
showToast('#ó Sari entries save ho rahi hain...');
try {
const r = await fetch(`${FLASK_API}/save_all`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: [Link]({ entries: [Link] })
});
const d = await [Link]();
if ([Link] === 'ok') {
let msg = `' ${[Link]} entries saved: ${[Link]}`;
if ([Link]?.status === 'success') msg += ' · &þ Drive!';
showToast(msg);
}
} catch {
showToast(''L Server se connect nahi hua!');
}
}
// =====================================
// DRIVE UPLOAD WITH PROGRESS
// =====================================
let driveUploadedFiles = []; // Drive pe existing files ki list
async function refreshDriveStatus() {
showToast('#ó Drive status check ho raha hai...');
try {
const r = await fetch(`${FLASK_API}/drive_status`);
const d = await [Link]();
if ([Link] === 'ok') {
driveUploadedFiles = [Link] || [];
renderDriveStatusList();
showToast(`' Drive check ho gaya — ${[Link]}
files mili`);
} else if ([Link] === 'disabled') {
showToast('& þ rclone disabled hai — Settings mein enable karo');
} else {
showToast(''L Drive check fail: ' + ([Link] || 'unknown error'));
}
} catch {
showToast(''L Server se connect nahi hua!');
}
}
function entryFilename(entry) {
const slug = ([Link] || 'entry').replace(/[^a-zA-Z0-9]/g,
'_').toLowerCase().slice(0, 40);
const dateStr = ([Link] || new Date().toISOString()).slice(0, 10);
const entryId = ([Link] || 'unknown').slice(0, 8);
return `${dateStr}_${slug}_${entryId}.json`;
}
function isEntryOnDrive(entry) {
const entryId = ([Link] || '').slice(0, 8);
const dateStr = ([Link] || '').slice(0, 10);
const exactName = entryFilename(entry);
return [Link](f => {
const fname = [Link]().toLowerCase();
// 1. Exact match
if (fname === [Link]()) return true;
// 2. Entry ID se match (sabse reliable)
if (entryId && [Link]([Link]())) return true;
// 3. Date + title ke pehle 10 chars se match
if (dateStr && [Link](dateStr)) {
const titleSlug = ([Link] || '').replace(/[^a-zA-Z0-9]/g,
'_').toLowerCase().slice(0, 10);
if (titleSlug && [Link](titleSlug)) return true;
}
return false;
});
}
function renderDriveStatusList() {
const section = [Link]('drive-status-section');
const listEl = [Link]('drive-entries-list');
if (!section || !listEl) return;
[Link] = '';
const uploaded = [Link](e => isEntryOnDrive(e));
const pending = [Link](e => !isEntryOnDrive(e));
[Link]('drive-stat-uploaded').textContent = `'
${[Link]} Uploaded`;
[Link]('drive-stat-pending').textContent = `#ó
${[Link]} Pending`;
[Link] = '';
// Drive pe total files info
if ([Link] > 0) {
const infoDiv = [Link]('div');
[Link] =
'font-size:0.72rem;color:var(--text2);margin-bottom:8px;padding:8px
12px;background:var(--surface2);border-radius:8px;';
[Link] = `<b>Drive pe mili files
(${[Link]}):</b>
${[Link](0,3).join(', ')}${[Link] > 3
? ` ...+${[Link]-3} aur` : ''}`;
[Link](infoDiv);
}
// Pending entries pehle
[...pending, ...uploaded].forEach(entry => {
const onDrive = isEntryOnDrive(entry);
const card = [Link]('div');
[Link] =
`display:flex;align-items:center;gap:10px;padding:10px
14px;border-radius:10px;border:1px solid ${onDrive ? '#a5d6a7' :
'var(--border)'};background:${onDrive ?
([Link]('dark') ? '#1a3a1f' : '#f1f8f1') :
'var(--surface)'};margin-bottom:2px;`;
const statusDot = [Link]('div');
[Link] = `width:10px;height:10px;border-radius:50%;
flex-shrink:0;background:${onDrive ? '#4caf50' : 'var(--border)'};`;
const info = [Link]('div');
[Link] = 'flex:1;min-width:0;';
[Link] = `<div style="font-size:0.85rem;font-weight:600;color
:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">$
{[Link] || 'Untitled'}</div>
<div style="font-size:0.72rem;color:var(--text2);font-family:var(--font-
mono);">${([Link]||'').slice(0,10)} · ${onDrive ? '&þ Drive pe hai' : '#ó
Upload nahi hua'}</div>`;
[Link](statusDot);
[Link](info);
if (!onDrive) {
const btn = [Link]('button');
[Link] = 'btn btn-outline btn-sm';
[Link] = '&þ Upload';
[Link] = `drive-upload-btn-${[Link]}`;
[Link] = () => uploadSingleEntryToDrive(entry);
[Link](btn);
} else {
const badge = [Link]('span');
[Link] =
'font-size:0.72rem;color:#4caf50;font-weight:700;white-space:nowrap;';
[Link] = '' Done';
[Link](badge);
}
[Link](card);
});
}
async function uploadSingleEntryToDrive(entry) {
const btn = [Link](`drive-upload-btn-${[Link]}`);
if (btn) { [Link] = true; [Link] = '#ó...'; }
try {
const r = await fetch(`${FLASK_API}/upload_entry`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: [Link]({ entry })
});
const d = await [Link]();
if ([Link] === 'success') {
[Link]([Link]);
showToast(`' "${[Link]}" Drive pe upload ho gaya!`);
renderDriveStatusList();
} else {
showToast(''L Upload fail: ' + ([Link] || 'error'));
if (btn) { [Link] = false; [Link] = '&þ Upload'; }
}
} catch {
showToast(''L Server se connect nahi hua!');
if (btn) { [Link] = false; [Link] = '&þ Upload'; }
}
}
async function uploadAllToDrive() {
if (![Link]) { showToast('& þ Koi entry nahi!'); return; }
const entriesToUpload = [Link]; // sab upload karo
await runDriveUpload(entriesToUpload);
}
async function uploadSelectedToDrive() {
const ids = getSelectedIds();
if (![Link]) { showToast('& þ Pehle entries select karo niche se!');
return; }
const selected = [Link](e => [Link]([Link]));
await runDriveUpload(selected);
}
async function runDriveUpload(entries) {
if (![Link]) return;
const wrap = [Link]('upload-progress-wrap');
const bar = [Link]('upload-progress-bar');
const label = [Link]('upload-progress-label');
const countEl = [Link]('upload-progress-count');
const currentEl = [Link]('upload-current-entry');
const summaryEl =
[Link]('upload-result-summary');
[Link] = '';
[Link] = 'none';
[Link] = '0%';
[Link] = 'Uploading...';
let success = 0, failed = 0;
const failedEntries = [];
for (let i = 0; i < [Link]; i++) {
const entry = entries[i];
const pct = [Link](((i) / [Link]) * 100);
[Link] = pct + '%';
[Link] = `${i} / ${[Link]}`;
[Link] = `Ø=ÜÝ "${[Link] || 'Untitled'}" upload ho raha
hai...`;
try {
const r = await fetch(`${FLASK_API}/upload_entry`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: [Link]({ entry })
});
const d = await [Link]();
if ([Link] === 'success') {
success++;
if ()
[Link]([Link]);
} else {
failed++;
[Link]([Link] || 'Untitled');
}
} catch {
failed++;
[Link]([Link] || 'Untitled');
}
}
// 100% complete
[Link] = '100%';
[Link] = `${[Link]} / ${[Link]}`;
[Link] = '';
[Link] = failed === 0 ? '' Upload complete!' : `& þ ${failed}
fail hua`;
// Summary
[Link] = '';
if (failed === 0) {
[Link] = 'display:block;padding:10px
14px;border-radius:10px;background:#e8f5e9;border:1px solid
#a5d6a7;font-size:0.82rem;color:#2e7d32;';
[Link] = `' <b>${success} entries</b> successfully
Google Drive pe upload ho gayi!`;
} else {
[Link] = 'display:block;padding:10px
14px;border-radius:10px;background:#fff3e0;border:1px solid
#ffcc80;font-size:0.82rem;color:#e65100;';
[Link] = `& þ <b>${success} success</b>,
<b>${failed} fail</b><br><span style="font-size:0.75rem;">Failed:
${[Link](0,3).join(', ')}${[Link]>3?'
...':''}</span>`;
}
// Drive status list refresh
renderDriveStatusList();
setTimeout(() => { [Link] = 'none'; }, 3000);
}
function saveState() {
[Link]('journal_data', [Link](state));
// Category questions ko bhi Flask par save karo (async, silent)
if ([Link]) {
fetch(FLASK_API + '/category_questions', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: [Link]({categoryQuestions:
[Link]})
}).catch(function(){});
}
// Flask server pe bhi save karo (device storage)
flaskSaveState();
}
async function flaskSaveState() {
try {
// Entries save karo
await fetch(`${FLASK_API}/entries`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: [Link]({ entries: [Link] })
});
// Settings (categories, moods, questions, eras, theme etc.) save karo
await fetch(`${FLASK_API}/settings`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: [Link]({ settings: [Link] })
});
} catch(e) {
// Server nahi mila — sirf localStorage pe hai, koi baat nahi
}
}
async function loadStateFromFlask() {
try {
const [eRes, sRes, erasRes, cqRes] = await [Link]([
fetch(`${FLASK_API}/entries`),
fetch(`${FLASK_API}/settings`),
fetch(`${FLASK_API}/eras`),
fetch(`${FLASK_API}/category_questions`)
]);
if ([Link]) {
const ed = await [Link]();
if ([Link] && [Link] > 0) {
[Link] = [Link];
}
}
if ([Link]) {
const sd = await [Link]();
if ([Link]) {
[Link] = { ...[Link], ...[Link] };
}
}
// Eras alag file se load karo (agar settings mein nahi hain)
if ([Link]) {
const ed = await [Link]();
if ([Link] && [Link] > 0) {
[Link] = [Link];
}
}
// Category Questions alag file se load karo
if (cqRes && [Link]) {
try {
const cqd = await [Link]();
if ([Link] &&
[Link]([Link]).length > 0) {
[Link] = [Link];
}
} catch(e) {}
}
// LocalStorage bhi update karo
[Link]('journal_data', [Link](state));
} catch(e) {
// Flask nahi mila — localStorage se hi chalega
}
}
function loadState() {
try {
const raw = [Link]('journal_data');
if (raw) {
const saved = [Link](raw);
[Link] = [Link] || [];
if ([Link]) {
[Link] = { ...[Link], ...[Link] };
}
}
} catch(e) {}
}
function genId() { return [Link]().toString(36) +
[Link]().toString(36).substr(2,5); }
// =====================================
// DRAFT AUTOSAVE
// =====================================
const DRAFT_KEY = 'journal_draft';
let draftTimer = null;
function saveDraft() {
const draft = {
title: [Link]('entry-title')?.value || '',
mood: selectedMood,
category: [Link]('entry-category')?.value || '',
tags: [...newEntryTags],
cqAnswers: {},
cqSeconds: {},
savedAt: [Link]()
};
const _draftCat = ([Link]('entry-category') ?
[Link]('entry-category').value : '').trim();
const _draftGlobal = [Link] || [];
const _draftCatQs = ([Link] && _draftCat
&& [Link][_draftCat]) || [];
const _draftAllQs = [..._draftGlobal];
_draftCatQs.forEach(function(q){ if (!_draftAllQs.includes(q))
_draftAllQs.push(q); });
_draftAllQs.forEach(function(q, i) {
const el = [Link]('cq-ans-' + i);
[Link][i] = el ? [Link] : '';
[Link][i] = cqTimers[i] ? cqTimers[i].seconds : 0;
});
[Link](DRAFT_KEY, [Link](draft));
// Flask pe bhi save karo
fetch(`${FLASK_API}/draft`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: [Link]({ draft })
}).catch(() => {});
const ds = [Link]('draft-status');
if (ds) {
const t = new Date().toLocaleTimeString('en-US', {hour:'2-digit',
minute:'2-digit'});
[Link] = '' Draft saved ' + t;
}
}
function clearDraft() {
[Link](DRAFT_KEY);
// Flask se bhi delete karo
fetch(`${FLASK_API}/draft`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: [Link]({ draft: null })
}).catch(() => {});
const ds = [Link]('draft-status');
if (ds) [Link] = '';
}
async function loadDraftFromFlask() {
try {
const r = await fetch(`${FLASK_API}/draft`);
if ([Link]) {
const d = await [Link]();
if ([Link]) {
[Link](DRAFT_KEY, [Link]([Link]));
return true;
}
}
} catch(e) {}
return false;
}
function loadDraft() {
try {
const raw = [Link](DRAFT_KEY);
if (!raw) return false;
const draft = [Link](raw);
// Check if draft is less than 7 days old
if ([Link]() - [Link] > 7 * 24 * 3600 * 1000) { clearDraft();
return false; }
// Restore title
const titleEl = [Link]('entry-title');
if (titleEl && [Link]) [Link] = [Link];
// Restore mood
if ([Link]) { selectedMood = [Link];
renderMoodGrid('mood-grid'); }
// Restore category
const catEl = [Link]('entry-category');
if (catEl && [Link]) [Link] = [Link];
// Restore tags
if ([Link] && [Link]) {
newEntryTags = [Link];
renderTagsInWrap('tags-wrap', newEntryTags, 'removeNewTag');
}
// Restore CQ answers + timer seconds (after questions rendered)
setTimeout(() => {
const _rCat = ([Link]('entry-category') ?
[Link]('entry-category').value : '').trim();
const _rGlobal = [Link] || [];
const _rCatQs = ([Link] && _rCat &&
[Link][_rCat]) || [];
const _rAllQs = [..._rGlobal];
_rCatQs.forEach(function(q){ if (!_rAllQs.includes(q))
_rAllQs.push(q); });
_rAllQs.forEach(function(q, i) {
const el = [Link]('cq-ans-' + i);
if (el && [Link] && [Link][i] !== undefined) {
[Link] = [Link][i];
}
if (cqTimers[i] && [Link] && [Link][i]) {
cqTimers[i].seconds = [Link][i];
const m = String([Link](cqTimers[i].seconds / 60)).padStart(2,
'0');
const s = String(cqTimers[i].seconds % 60).padStart(2, '0');
const el2 = [Link]('cq-time-' + i);
if (el2) [Link] = m + ':' + s;
const badge = [Link]('cq-badge-' + i);
if (badge) [Link]('done');
}
});
// Update total timer display
const total = [Link](cqTimers).reduce((s, t) => s + [Link],
0);
if (total > 0) {
const tm = String([Link](total / 60)).padStart(2, '0');
const ts = String(total % 60).padStart(2, '0');
const wt = [Link]('writing-timer');
if (wt) [Link] = '#ñ ' + tm + ':' + ts;
}
// Show draft restored notice
const ds = [Link]('draft-status');
const ago = [Link](([Link]() - [Link]) / 60000);
const agoStr = ago < 1 ? 'abhi abhi' : ago + ' min pehle';
if (ds) [Link] = 'Ø=ÜÝ Draft restore hua (' + agoStr + ')';
}, 100);
return true;
} catch(e) { return false; }
}
function startDraftAutosave() {
// Save draft every 3 seconds when something changes
['entry-title', 'entry-category'].forEach(id => {
const el = [Link](id);
if (el) [Link]('input', scheduleDraftSave);
});
// For textareas inside cq-answers, use event delegation
[Link]('cq-answers')?.addEventListener('input',
scheduleDraftSave);
[Link]('tags-wrap')?.addEventListener('input',
scheduleDraftSave);
}
function scheduleDraftSave() {
clearTimeout(draftTimer);
draftTimer = setTimeout(saveDraft, 1500);
}
// =====================================
// INIT
// =====================================
function init() {
loadState(); // pehle localStorage se fast load
applyTheme();
applyFontSize();
renderMoodGrid('mood-grid');
renderMoodGrid('edit-mood-grid');
renderMoodPreview();
renderCategories();
populateCategorySelect('entry-category');
populateCategorySelect('edit-category');
renderCustomQuestionsForm();
renderSettingsCQ(); populateCatQSelector();
renderSettingsCats();
startClock();
startWritingTimer();
startDraftAutosave();
refreshPage();
// Flask config + data load (async)
loadFlaskConfig();
loadFlaskDataOnStartup();
loadBDFromFlask();
loadBDCategoriesFromFlask();
}
async function loadFlaskDataOnStartup() {
// Pehle flask se latest data load karo
await loadStateFromFlask();
// Draft bhi flask se load karo agar localStorage mein nahi
const localDraft = [Link](DRAFT_KEY);
if (!localDraft) {
await loadDraftFromFlask();
}
// Ab UI refresh karo fresh data ke saath
applyTheme();
applyFontSize();
renderMoodGrid('mood-grid');
renderMoodGrid('edit-mood-grid');
renderMoodPreview();
renderCategories();
populateCategorySelect('entry-category');
populateCategorySelect('edit-category');
renderCustomQuestionsForm();
renderSettingsCQ(); populateCatQSelector();
renderSettingsCats();
loadDraft();
refreshPage();
}
function showPage(name) {
[Link]('.page').forEach(p =>
[Link]('active'));
[Link]('.nav-item').forEach(n =>
[Link]('active'));
const page = [Link]('page-' + name);
if (page) [Link]('active');
const navIdx = ['new-entry','entries','favourites','eras','search','stats','bra
indump','other','backup','settings'];
const navItems = [Link]('.nav-item');
const idx = [Link](name);
if (idx >= 0 && navItems[idx]) navItems[idx].[Link]('active');
const titles = {'new-entry':'New Entry','entries':'All Entries','favourites':'+P
Favourites','eras':'Ø=ÝÓþ Era
Timeline','search':'Search','stats':'Statistics','braindump':'Ø>Ýà Thought
Capture','other':'Ø=ÝÂþ Other','backup':'Backup &
Restore','settings':'Settings'};
[Link]('page-title').textContent = titles[name] ||
name;
currentPage = name;
// sync bottom nav
[Link](".bnav-item").forEach(b =>
[Link]("active"));
const bnavEl = [Link]("bnav-" + name);
if (bnavEl) [Link]("active");
refreshPage();
}
function refreshPage() {
if (currentPage === 'entries') renderEntriesList();
if (currentPage === 'favourites') renderFavouritesList();
if (currentPage === 'eras') renderEraTimeline();
if (currentPage === 'stats') renderStats();
if (currentPage === 'braindump') renderBDList();
if (currentPage === 'backup') { renderBackupList();
renderLastBackupTimes(); if ([Link] > 0)
renderDriveStatusList(); }
if (currentPage === 'other') loadOtherList();
if (currentPage === 'search') doSearch();
if (currentPage === 'settings') { renderSettingsCQ();
populateCatQSelector(); renderSettingsCats(); renderMoodPreview(); }
}
// =====================================
// CLOCK
// =====================================
function startClock() {
function tick() {
const now = new Date();
const isMobile = [Link] <= 700;
[Link]('live-clock').textContent = isMobile
? [Link]('en-US', { hour:'2-digit', minute:'2-digit' })
: [Link]('en-US', { weekday:'short', month:'short',
day:'numeric', hour:'2-digit', minute:'2-digit', second:'2-digit' });
[Link]('entry-datetime').textContent = 'Ø=ÜÅ ' +
[Link]('en-US', { dateStyle:'medium', timeStyle:'short' });
}
tick(); setInterval(tick, 1000);
}
// =====================================
// WRITING TIMER
// =====================================
function startWritingTimer() {
// Writing timer is now driven by per-question CQ timers
(startCQTimer/pauseCQTimer)
// This function kept for compatibility
}
function onTextInput() {
if (!timerStarted) { timerStarted = true; }
}
// =====================================
// MOOD
// =====================================
let selectedMood = '';
let editSelectedMood = '';
function renderMoodGrid(containerId) {
const wrap = [Link](containerId);
if (!wrap) return;
[Link] = '';
[Link](mood => {
const el = [Link]('button');
[Link] = 'mood-chip';
[Link] = mood;
const isCurrent = containerId === 'mood-grid' ? selectedMood ===
mood : editSelectedMood === mood;
if (isCurrent) [Link]('selected');
[Link] = () => {
if (containerId === 'mood-grid') {
selectedMood = mood;
} else {
editSelectedMood = mood;
}
renderMoodGrid(containerId);
};
[Link](el);
});
}
function renderMoodPreview() {
const wrap = [Link]('settings-mood-preview');
if (!wrap) return;
[Link] = '';
[Link]((mood, i) => {
const el = [Link]('span');
[Link] = 'mood-chip';
[Link] = mood + ' <button onclick="removeMood(' + i + ')" styl
e="background:none;border:none;cursor:pointer;color:var(--text2);margi
n-left:4px;font-size:0.8rem;">'</button>';
[Link](el);
});
}
function addMood() {
const val = [Link]('new-mood-input').[Link]();
if (!val) return;
[Link](val);
[Link]('new-mood-input').value = '';
saveState(); renderMoodPreview(); renderMoodGrid('mood-grid');
renderMoodGrid('edit-mood-grid');
showToast('Mood added!');
}
function removeMood(i) {
[Link](i, 1);
saveState(); renderMoodPreview(); renderMoodGrid('mood-grid');
renderMoodGrid('edit-mood-grid');
}
// =====================================
// CATEGORIES
// =====================================
function renderCategories() {
populateCategorySelect('entry-category');
populateCategorySelect('edit-category');
populateCategorySelect('filter-cat');
renderSettingsCats();
}
function populateCategorySelect(id) {
const sel = [Link](id);
if (!sel) return;
const val = [Link];
[Link] = '<option value="">Select category…</option>';
[Link](c => {
const opt = [Link]('option');
[Link] = c; [Link] = c;
[Link](opt);
});
[Link] = val;
}
function renderSettingsCats() {
const wrap = [Link]('settings-cat-list');
if (!wrap) return;
[Link] = '';
[Link]((c, i) => {
const el = [Link]('span');
[Link] = 'cat-badge';
[Link] = c + ' <button onclick="removeCategory(' + i +
')">'</button>';
[Link](el);
});
}
function addCategory() {
const val = [Link]('new-cat-input').[Link]();
if (!val || [Link](val)) return;
[Link](val);
[Link]('new-cat-input').value = '';
saveState(); renderCategories(); showToast('Category added!');
}
function promptAddCategory() {
const val = prompt('Enter new category name:');
if (val && [Link]() && )) {
[Link]([Link]());
saveState(); renderCategories(); showToast('Category "' + [Link]() +
'" added!');
[Link]('entry-category').value = [Link]();
}
}
function removeCategory(i) {
[Link](i, 1);
saveState(); renderCategories();
}
// =====================================
// CUSTOM QUESTIONS
// =====================================
// per-question entry timers (separate from qTimers used in questions
page)
let cqTimers = {};
function renderCustomQuestionsForm() {
const answersDiv = [Link]('cq-answers');
const emptyMsg = [Link]('cq-empty');
if (!answersDiv) return;
// Global questions
const globalQs = [Link] || [];
// Category-specific questions
const selectedCat = ([Link]('entry-category') ?
[Link]('entry-category').value : '').trim();
const catQs = ([Link] && selectedCat &&
[Link][selectedCat]) || [];
// Merge: global first, then category-specific (deduplicated)
const allQs = [...globalQs];
[Link](function(q){ if () [Link](q); });
if ([Link] === 0) {
[Link] = '';
if (emptyMsg) [Link] = 'block';
return;
}
if (emptyMsg) [Link] = 'none';
// Stop any old timers
[Link](cqTimers).forEach(function(t){ if ([Link])
clearInterval([Link]); });
cqTimers = {};
[Link] = '';
// Show category banner if cat-specific questions exist
if ([Link] > 0 && selectedCat) {
const catBanner = [Link]('div');
[Link] = 'font-size:0.78rem;font-weight:600;color:var
(--accent);text-transform:uppercase;letter-spacing:0.07em;margin-botto
m:8px;padding:6px 10px;background:var(--accent-light);border-radius:8
px;display:inline-block;';
[Link] = '\uD83D\uDCC2 ' + selectedCat + ' ke
questions bhi include hain';
[Link](catBanner);
}
[Link](function(q, i) {
const isCatQ = [Link](q) && ;
cqTimers[i] = { seconds: 0, interval: null };
const div = [Link]('div');
[Link] = 'q-session-card';
[Link] = 'cq-card-' + i;
if (isCatQ) [Link] = '3px solid var(--accent)';
[Link] =
'<div class="q-session-header">' +
'<div class="q-number">' + (i + 1) + '</div>' +
'<div class="q-text">' + q + (isCatQ ? ' <span style="font-size:0.7re
m;color:var(--accent);font-weight:600;margin-left:6px;">Ø=ÜÂ ' +
selectedCat + '</span>' : '') + '</div>' +
'<div class="q-timer-badge" id="cq-badge-' + i + '">' +
'<div class="q-timer-dot"></div>' +
'<span id="cq-time-' + i + '">00:00</span>' +
'</div>' +
'</div>' +
'<textarea id="cq-ans-' + i + '" placeholder="Yahan apna jawab
likhein\u2026" style="min-height:90px;width:100%;"
onfocus="startCQTimer(' + i + ')" onblur="pauseCQTimer(' + i + ')"
oninput="scheduleDraftSave()"></textarea>';
[Link](div);
});
}
function startCQTimer(i) {
const t = cqTimers[i];
if (!t || [Link]) return;
[Link] = setInterval(() => {
[Link]++;
// update this question's badge
const el = [Link]('cq-time-' + i);
if (el) {
const m = String([Link]([Link] / 60)).padStart(2, '0');
const s = String([Link] % 60).padStart(2, '0');
[Link] = m + ':' + s;
}
const badge = [Link]('cq-badge-' + i);
if (badge) [Link]('running');
[Link]('cq-card-' + i)?.[Link]('active-q');
// also update total writing-timer
const total = [Link](cqTimers).reduce((s, t) => s + [Link],
0);
const tm = String([Link](total / 60)).padStart(2, '0');
const ts = String(total % 60).padStart(2, '0');
const wt = [Link]('writing-timer');
if (wt) [Link] = '#ñ ' + tm + ':' + ts;
}, 1000);
}
function pauseCQTimer(i) {
const t = cqTimers[i];
if (!t || ![Link]) return;
clearInterval([Link]);
[Link] = null;
const badge = [Link]('cq-badge-' + i);
if (badge) { [Link]('running');
[Link]('done'); }
[Link]('cq-card-' + i)?.[Link]('active-q');
}
function renderSettingsCQ() {
const list = [Link]('settings-cq-list');
if (!list) return;
[Link] = '';
const qs = [Link];
if ([Link] === 0) {
[Link] = '<p style="font-size:0.82rem;color:var(--text2);">No
global questions yet.</p>';
return;
}
[Link](function(q, i) {
const div = [Link]('div');
[Link] = 'cq-list-item';
[Link] =
'display:flex;align-items:center;gap:6px;padding:8px 10px;background:v
ar(--surface2);border-radius:8px;margin-bottom:6px;border:1px solid
var(--border);';
[Link] =
'<div
style="display:flex;flex-direction:column;gap:2px;margin-right:2px;">' +
'<button class="btn btn-ghost btn-sm" style="padding:1px
5px;font-size:0.7rem;" onclick="moveCQ('+i+',-1)"
title="Upar">%²</button>' +
'<button class="btn btn-ghost btn-sm" style="padding:1px
5px;font-size:0.7rem;" onclick="moveCQ('+i+',1)"
title="Neeche">%¼</button>' +
'</div>' +
'<span style="flex:1;font-size:0.85rem;">'+q+'</span>' +
'<button class="btn btn-ghost btn-sm" onclick="removeCQ('+i+')"
style="color:var(--danger);">'</button>';
[Link](div);
});
}
function moveCQ(i, dir) {
const qs = [Link];
const j = i + dir;
if (j < 0 || j >= [Link]) return;
[qs[i], qs[j]] = [qs[j], qs[i]];
saveState(); renderSettingsCQ(); renderCustomQuestionsForm();
}
function addCustomQuestion() {
const val = [Link]('new-cq-input').[Link]();
if (!val) return;
[Link](val);
[Link]('new-cq-input').value = '';
saveState(); renderSettingsCQ(); renderCustomQuestionsForm();
showToast('Question added!');
}
function removeCQ(i) {
[Link](i, 1);
saveState(); renderSettingsCQ(); renderCustomQuestionsForm();
}
// =====================================
// CATEGORY-WISE QUESTIONS
// =====================================
function populateCatQSelector() {
const sel = [Link]('catq-category-sel');
if (!sel) return;
const current = [Link];
[Link] = '<option value="">-- Category chunein --</option>';
([Link] || []).forEach(function(c) {
const opt = [Link]('option');
[Link] = c;
const cnt = ([Link] &&
[Link][c] &&
[Link][c].length) || 0;
[Link] = c + (cnt > 0 ? ' (' + cnt + ')' : '');
if (c === current) [Link] = true;
[Link](opt);
});
renderCategoryQList();
}
function renderCategoryQList() {
const sel = [Link]('catq-category-sel');
const list = [Link]('catq-list');
const addWrap = [Link]('catq-add-wrap');
const badge = [Link]('catq-count-badge');
if (!sel || !list) return;
const cat = [Link];
if (!cat) {
[Link] = '<p
style="font-size:0.82rem;color:var(--text2);">Pehle category select
karein.</p>';
if (addWrap) [Link] = 'none';
if (badge) [Link] = '';
return;
}
if (![Link]) [Link]
= {};
const qs = [Link][cat] || [];
if (badge) [Link] = [Link] ? [Link] + ' question(s)' : '';
[Link] = '';
if ([Link] === 0) {
[Link] = '<p style="font-size:0.82rem;color:var(--text2);">"' +
cat + '" ke liye koi question nahi abhi.</p>';
} else {
[Link](function(q, i) {
const div = [Link]('div');
[Link] =
'display:flex;align-items:center;gap:6px;padding:8px 10px;background:v
ar(--accent-light);border-radius:8px;margin-bottom:6px;border:1px solid
var(--border);';
[Link] =
'<div
style="display:flex;flex-direction:column;gap:2px;margin-right:2px;">' +
'<button class="btn btn-ghost btn-sm" style="padding:1px
5px;font-size:0.7rem;"
onclick="moveCatQ(\''+cat+'\','+i+',-1)">%²</button>' +
'<button class="btn btn-ghost btn-sm" style="padding:1px
5px;font-size:0.7rem;" onclick="moveCatQ(\''+cat+'\','+i+',1)">%¼</button>'
+
'</div>' +
'<span style="flex:1;font-size:0.85rem;">'+q+'</span>' +
'<span style="font-size:0.7rem;color:var(--accent);font-weight:600;
margin-right:4px;">Ø=ÜÂ '+cat+'</span>' +
'<button class="btn btn-ghost btn-sm"
onclick="removeCatQ(\''+cat+'\','+i+')"
style="color:var(--danger);">'</button>';
[Link](div);
});
}
if (addWrap) [Link] = 'flex';
}
function addCategoryQuestion() {
const sel = [Link]('catq-category-sel');
const inp = [Link]('new-catq-input');
if (!sel || !inp) return;
const cat = [Link]();
const val = [Link]();
if (!cat) { showToast('& þ Pehle category select karein!'); return; }
if (!val) { showToast('& þ Question likhein!'); return; }
if (![Link]) [Link]
= {};
if (![Link][cat])
[Link][cat] = [];
[Link][cat].push(val);
[Link] = '';
saveState(); renderCategoryQList(); populateCatQSelector();
renderCustomQuestionsForm();
showToast('' "' + cat + '" mein question add hua!');
}
function removeCatQ(cat, i) {
if (![Link] ||
![Link][cat]) return;
[Link][cat].splice(i, 1);
if ([Link][cat].length === 0) delete
[Link][cat];
saveState(); renderCategoryQList(); populateCatQSelector();
renderCustomQuestionsForm();
}
function moveCatQ(cat, i, dir) {
const qs = [Link] &&
[Link][cat];
if (!qs) return;
const j = i + dir;
if (j < 0 || j >= [Link]) return;
const tmp = qs[i]; qs[i] = qs[j]; qs[j] = tmp;
saveState(); renderCategoryQList(); renderCustomQuestionsForm();
}
// =====================================
// TAGS
// =====================================
function renderTagsInWrap(wrapId, tags, removeFn) {
const wrap = [Link](wrapId);
const input = [Link]('.tags-input');
// remove old pills
[Link]('.tag-pill').forEach(p => [Link]());
[Link]((t, i) => {
const pill = [Link]('span');
[Link] = 'tag-pill';
[Link] = '#' + t + `<button
onclick="${removeFn}(${i})">'</button>`;
[Link](pill, input);
});
}
function handleTagInput(e) {
if ([Link] === 'Enter' || [Link] === ',') {
[Link]();
const val = [Link]().replace(/,/g,'');
if (val && ) {
[Link](val);
renderTagsInWrap('tags-wrap', newEntryTags, 'removeNewTag');
}
[Link] = '';
}
}
function removeNewTag(i) {
[Link](i, 1);
renderTagsInWrap('tags-wrap', newEntryTags, 'removeNewTag');
}
function handleEditTagInput(e) {
if ([Link] === 'Enter' || [Link] === ',') {
[Link]();
const val = [Link]().replace(/,/g,'');
if (val && ) {
[Link](val);
renderTagsInWrap('edit-tags-wrap', editTags, 'removeEditTag');
}
[Link] = '';
}
}
function removeEditTag(i) {
[Link](i, 1);
renderTagsInWrap('edit-tags-wrap', editTags, 'removeEditTag');
}
// =====================================
// SAVE ENTRY
// =====================================
function saveEntry() {
const title = [Link]('entry-title').[Link]();
if (!title) {
[Link]('entry-title').focus();
[Link]('entry-title').[Link] =
'var(--danger)';
showToast('& þ Pehle title likhein!');
setTimeout(() => {
[Link]('entry-title').[Link] = ''; }, 2000);
return;
}
const category = [Link]('entry-category').value;
const now = new Date();
// Collect CQ answers — global + category-specific merged
const globalQsSave = [Link] || [];
const catQsSave = ([Link] && category &&
[Link][category]) || [];
const allQsSave = [...globalQsSave];
[Link](function(q){ if ()
[Link](q); });
const cqAnswers = {};
const cqTimes = {};
[Link](function(q, i) {
const el = [Link]('cq-ans-' + i);
cqAnswers[q] = el ? [Link]() : '';
cqTimes[q] = cqTimers[i] ? cqTimers[i].seconds : 0;
});
// Main text from the text area (not from Q&A - those are stored
separately in cqAnswers)
const text = ([Link]('entry-text')?.value || '').trim();
const totalTime = [Link](cqTimers).reduce((s, t) => s +
[Link], 0);
const allText = text + ' ' + [Link](cqAnswers).join(' ');
const wordCount = [Link]().split(/ /).filter(Boolean).length;
const entry = {
id: genId(),
title,
text,
mood: selectedMood,
category,
tags: [...newEntryTags],
cqAnswers,
cqTimes,
wordCount,
writingTime: totalTime,
date: [Link](),
created: [Link]()
};
[Link](entry);
saveState();
// Flask se local + rclone save (background mein)
saveEntryToFlask(entry, 'json', true);
pendingSaveEntry = entry;
selectedSaveFmt = 'json';
selectFmt('json');
showModal('save-local-modal');
showToast('' Entry saved!');
clearEntry();
}
function clearEntry() {
[Link]('entry-title').value = '';
[Link]('entry-text').value = '';
const cat = [Link]('entry-category');
if (cat) [Link] = '';
selectedMood = '';
newEntryTags = [];
renderMoodGrid('mood-grid');
renderTagsInWrap('tags-wrap', newEntryTags, 'removeNewTag');
// reset total timer display
timerSeconds = 0; timerStarted = false;
const wt = [Link]('writing-timer');
if (wt) [Link] = '#ñ 00:00';
// Stop and clear all CQ timers + re-render fresh
[Link](cqTimers).forEach(t => { if ([Link])
clearInterval([Link]); });
cqTimers = {};
renderCustomQuestionsForm();
clearDraft();
}
// =====================================
// LOCAL SAVE (Save As dialog)
// =====================================
let selectedSaveFmt = 'json';
function selectFmt(fmt) {
selectedSaveFmt = 'json';
[Link]('local-save-format').value = 'json';
}
async function saveWithDialog(entry, fmt) {
const slug = ([Link] ||
'entry').replace(/[^a-z0-9]/gi,'_').toLowerCase().slice(0,40);
const filename = slug + '_' + new
Date([Link]).toISOString().slice(0,10) + '.json';
const fileContent = [Link](entry, null, 2);
if ([Link]) {
try {
const fh = await [Link]({
suggestedName: filename,
types: [{ description: 'JSON File', accept: { 'application/json': ['.json']
} }]
});
const writable = await [Link]();
await [Link](fileContent);
await [Link]();
showToast('' File save ho gayi!');
return;
} catch(e) {
if ([Link] === 'AbortError') return;
}
}
downloadFile(fileContent, filename, 'application/json');
showToast('' File download ho rahi hai!');
}
async function confirmLocalSave() {
const fmt = 'json';
if (pendingSaveEntry) await saveWithDialog(pendingSaveEntry, fmt);
pendingSaveEntry = null;
closeModal('save-local-modal');
}
// =====================================
// ENTRIES LIST
// =====================================
function renderEntriesList() {
const container = [Link]('entries-list-container');
if (!container) return;
if ([Link] === 0) {
[Link] = `<div class="empty-state"><div
class="icon">Ø=ÜÖ</div><p>No entries yet. Start writing!</p></div>`;
return;
}
[Link] = '';
[Link](entry => {
[Link](makeEntryCard(entry, false));
});
}
function makeEntryCard(entry, forBackup=false) {
const card = [Link]('div');
[Link] = 'entry-card';
const date = new Date([Link]).toLocaleString('en-US', {
dateStyle:'medium', timeStyle:'short' });
const tagsHtml = ([Link]||[]).map(t => `<span
class="meta-badge">#${t}</span>`).join('');
const wt = [Link] ? formatTime([Link]) : '—';
[Link] = `
<div class="entry-card-header">
${forBackup ? `<input type="checkbox" class="entry-checkbox
backup-checkbox" data-id="${[Link]}">` : ''}
<div style="flex:1">
<div class="entry-card-title">${[Link] ? '+P ' : ''}${[Link] ||
'Untitled'}</div>
<div class="entry-card-meta">
<span class="meta-badge">Ø=ÜÅ ${date}</span>
${[Link] ? `<span class="meta-badge
mood">${[Link]}</span>` : ''}
${[Link] ? `<span class="meta-badge cat">Ø=ÝÂ
${[Link]}</span>` : ''}
<span class="meta-badge">Ø=ÜÝ ${[Link]||0}
words</span>
<span class="meta-badge">#ñ ${wt}</span>
${tagsHtml}
</div>
</div>
${!forBackup ? `<button class="star-btn ${[Link]?'starred':''}"
onclick="[Link]();toggleStar('${[Link]}')"
title="Favourite">&</button>` : ''}
</div>
<div class="entry-card-preview">${[Link] || ([Link] ?
[Link]([Link]).find(a=>a) || '' : '')}</div>
${!forBackup ? `<div class="entry-card-actions">
<button class="btn btn-outline btn-sm"
onclick="openDetail('${[Link]}')">Ø=ÜA View</button>
<button class="btn btn-outline btn-sm"
onclick="openEdit('${[Link]}')">' Edit</button>
<button class="btn btn-outline btn-sm"
onclick="downloadEntry(getEntry('${[Link]}'),'json')">+ JSON</button>
<button class="btn btn-danger btn-sm"
onclick="deleteEntry('${[Link]}')">Ø=ÝÑ</button>
</div>` : ''}
`;
if (!forBackup) [Link]('dblclick', () =>
openDetail([Link]));
return card;
}
function formatTime(secs) {
if (!secs) return '0s';
if (secs < 60) return secs + 's';
const m = [Link](secs/60); const s = secs%60;
return m + 'm ' + (s>0?s+'s':'');
}
function getEntry(id) { return [Link](e => [Link] === id); }
// =====================================
// DETAIL VIEW
// =====================================
function openDetail(id) {
currentDetailId = id;
const entry = getEntry(id);
if (!entry) return;
const date = new Date([Link]).toLocaleString('en-US', {
dateStyle:'full', timeStyle:'medium' });
const tagsHtml = ([Link]||[]).map(t => `<span
class="meta-badge">#${t}</span>`).join('');
const wt = [Link] ? formatTime([Link]) : '—';
let cqHtml = '';
if ([Link] && [Link]([Link]).length > 0) {
cqHtml = `<div class="detail-qa"><h4>Custom Questions</h4>`;
for (const [q, a] of [Link]([Link])) {
if (a) cqHtml += `<div class="qa-pair"><div class="q">Q:
${q}</div><div class="a">${a}</div></div>`;
}
cqHtml += '</div>';
}
[Link]('detail-modal-content').innerHTML = `
<h2 class="modal-title" style="padding-right:32px">${[Link] ||
'Untitled'}</h2>
<div class="detail-meta">
<span class="meta-badge">Ø=ÜÅ ${date}</span>
${[Link] ? `<span class="meta-badge
mood">${[Link]}</span>` : ''}
${[Link] ? `<span class="meta-badge cat">Ø=ÝÂ
${[Link]}</span>` : ''}
<span class="meta-badge">Ø=ÜÝ ${[Link]||0} words</span>
<span class="meta-badge">#ñ ${wt}</span>
${tagsHtml}
</div>
${cqHtml}
${[Link] ? `<div class="detail-content">${[Link]}</div>` : ''}
`;
showModal('detail-modal');
const starBtn = [Link]('detail-star-btn');
if (starBtn) {
[Link] = [Link] ? '& Starred' : '& Favourite';
[Link] = [Link] ? '#f5c518' : '';
[Link] = [Link] ? '#f5c518' : '';
}
}
function editCurrentEntry() { if (currentDetailId) {
closeModal('detail-modal'); openEdit(currentDetailId); } }
function deleteCurrentEntry() {
if (currentDetailId && confirm('Delete this entry?')) {
deleteEntry(currentDetailId);
closeModal('detail-modal');
}
}
function exportEntry(fmt) {
if (currentDetailId) downloadEntry(getEntry(currentDetailId), fmt);
}
// =====================================
// EDIT ENTRY
// =====================================
function openEdit(id) {
const entry = getEntry(id);
if (!entry) return;
currentDetailId = id;
[Link]('edit-title').value = [Link] || '';
[Link]('edit-text').value = [Link] || '';
editSelectedMood = [Link] || '';
editTags = [...([Link]||[])];
renderMoodGrid('edit-mood-grid');
renderTagsInWrap('edit-tags-wrap', editTags, 'removeEditTag');
populateCategorySelect('edit-category');
[Link]('edit-category').value = [Link] || '';
showModal('edit-modal');
}
function saveEdit() {
const entry = getEntry(currentDetailId);
if (!entry) return;
[Link] = [Link]('edit-title').[Link]();
[Link] = [Link]('edit-text').[Link]();
[Link] = editSelectedMood;
[Link] = [Link]('edit-category').value;
[Link] = [...editTags];
[Link] = [Link] ?
[Link]().split(/ /).filter(Boolean).length : 0;
saveState();
closeModal('edit-modal');
showToast('' Entry updated!');
refreshPage();
}
// =====================================
// DELETE
// =====================================
function deleteEntry(id) {
if (!confirm('Delete this entry? This cannot be undone.')) return;
[Link] = [Link](e => [Link] !== id);
saveState(); refreshPage(); showToast('Entry deleted.');
}
// =====================================
// SEARCH
// =====================================
function doSearch() {
const q = ([Link]('search-q')?.value ||
'').toLowerCase();
const mood = [Link]('filter-mood')?.value || '';
const cat = [Link]('filter-cat')?.value || '';
const from = [Link]('filter-date-from')?.value;
const to = [Link]('filter-date-to')?.value;
// Populate filter dropdowns — always refresh moods from settings
const moodSel = [Link]('filter-mood');
if (moodSel) {
const curMood = [Link];
[Link] = '<option value="">All Moods</option>';
[Link](m => { const o =
[Link]('option'); [Link]=m; [Link]=m;
[Link](o); });
[Link] = curMood;
}
const catSel = [Link]('filter-cat');
if (catSel && [Link] <= 1) {
[Link](c => { const o =
[Link]('option'); [Link]=c; [Link]=c;
[Link](o); });
}
let results = [Link];
if (q) results = [Link](e =>
([Link]||'').toLowerCase().includes(q) ||
([Link]||'').toLowerCase().includes(q) ||
([Link]||[]).some(t => [Link]().includes(q)) ||
([Link]||'').toLowerCase().includes(q)
);
if (mood) results = [Link](e => [Link] === mood);
if (cat) results = [Link](e => [Link] === cat);
if (from) results = [Link](e => new Date([Link]) >= new
Date(from));
if (to) results = [Link](e => new Date([Link]) <= new Date(to +
'T23:59:59'));
const container = [Link]('search-results');
if (!container) return;
if ([Link] === 0) {
[Link] = `<div class="empty-state"><div
class="icon">Ø=Ý</div><p>No entries found matching your
filters.</p></div>`;
return;
}
[Link] = `<p style="font-size:0.82rem;color:var(--text2);
margin-bottom:12px;">${[Link]} result${[Link]!==1?'s':''}
found</p>`;
[Link](e => [Link](makeEntryCard(e, false)));
}
function clearSearch() {
[Link]('search-q').value = '';
[Link]('filter-mood').value = '';
[Link]('filter-cat').value = '';
[Link]('filter-date-from').value = '';
[Link]('filter-date-to').value = '';
doSearch();
}
// =====================================
// STATISTICS
// =====================================
function renderStats() {
const entries = [Link];
const total = [Link];
const totalWords = [Link]((s, e) => s + ([Link]||0), 0);
const avgWords = total ? [Link](totalWords / total) : 0;
const totalTime = [Link]((s, e) => s + ([Link]||0), 0);
const avgTime = total ? [Link](totalTime / total) : 0;
[Link]('stats-grid').innerHTML = `
<div class="stat-card"><div class="stat-value">${total}</div><div
class="stat-label">Total Entries</div></div>
<div class="stat-card"><div
class="stat-value">${[Link]()}</div><div
class="stat-label">Total Words</div></div>
<div class="stat-card"><div
class="stat-value">${avgWords}</div><div class="stat-label">Avg
Words/Entry</div></div>
<div class="stat-card"><div
class="stat-value">${formatTime(avgTime)}</div><div
class="stat-label">Avg Writing Time</div></div>
<div class="stat-card"><div
class="stat-value">${formatTime(totalTime)}</div><div
class="stat-label">Total Writing Time</div></div>
<div class="stat-card"><div
class="stat-value">${getStreak()}</div><div class="stat-label">Day
Streak Ø=Ý%</div></div>
`;
// Mood chart
const moodCounts = {};
[Link](e => { if ([Link]) moodCounts[[Link]] =
(moodCounts[[Link]]||0)+1; });
renderBarChart('mood-chart', moodCounts, total);
// Category chart
const catCounts = {};
[Link](e => { if ([Link]) catCounts[[Link]] =
(catCounts[[Link]]||0)+1; });
renderBarChart('cat-chart', catCounts, total);
// Activity last 7 days
const actMap = {};
for (let i=6; i>=0; i--) {
const d = new Date(); [Link]([Link]()-i);
const key = [Link]('en-US', {month:'short',
day:'numeric'});
actMap[key] = 0;
}
[Link](e => {
const d = new Date([Link]);
const key = [Link]('en-US', {month:'short',
day:'numeric'});
if ([Link](key)) actMap[key]++;
});
const maxAct = [Link](...[Link](actMap), 1);
renderBarChart('activity-chart', actMap, maxAct, true);
// ---- Thought Capture Stats ----
const bdItems = getBDItems();
const bdTotal = [Link];
const bdDone = [Link](i => [Link]).length;
const bdPending = bdTotal - bdDone;
const bdHigh = [Link](i => [Link] === 'high').length;
const bdGrid = [Link]('bd-stats-grid');
if (bdGrid) {
[Link] = `
<div class="stat-card"><div class="stat-value">${bdTotal}</div><div
class="stat-label">Total Captured</div></div>
<div class="stat-card"><div class="stat-value"
style="color:var(--success)">${bdDone}</div><div
class="stat-label">Completed</div></div>
<div class="stat-card"><div class="stat-value"
style="color:#f39c12">${bdPending}</div><div
class="stat-label">Pending</div></div>
<div class="stat-card"><div class="stat-value"
style="color:#e74c3c">${bdHigh}</div><div class="stat-label">High
Priority</div></div>
`;
}
// BD category breakdown
const bdCatCounts = {};
[Link](i => { const lbl = [Link] || [Link] ||
'Other'; bdCatCounts[lbl] = (bdCatCounts[lbl]||0)+1; });
renderBarChart('bd-cat-chart', bdCatCounts, bdTotal);
}
function renderBarChart(id, data, total, absolute=false) {
const wrap = [Link](id);
if (!wrap) return;
const sorted = [Link](data).sort((a,b) => b[1]-a[1]).slice(0,8);
if ([Link] === 0) { [Link] = '<p
style="font-size:0.82rem;color:var(--text2);">No data yet.</p>'; return; }
const max = absolute ? total : [Link](...[Link](e=>e[1]));
[Link] = [Link](([label, val]) => {
const pct = max ? [Link]((val/max)*100) : 0;
const disp = absolute ? val : [Link]((val/total)*100) + '%';
return `<div class="bar-row">
<div class="bar-label">${label}</div>
<div class="bar-track"><div class="bar-fill"
style="width:${pct}%"><span
class="bar-val">${disp}</span></div></div>
</div>`;
}).join('');
}
function getStreak() {
if (![Link]) return 0;
const days = [...new Set([Link](e => new
Date([Link]).toDateString()))];
[Link]((a,b) => new Date(b)-new Date(a));
let streak = 0;
let check = new Date();
for (const d of days) {
const dd = new Date(d);
const diff = [Link]((check - dd) / 86400000);
if (diff <= 1) { streak++; check = dd; }
else break;
}
return streak;
}
// =====================================
// BACKUP & RESTORE
// =====================================
function renderBackupList() {
const container = [Link]('backup-entries-list');
if (!container) return;
let entries = [Link];
// Apply active date filter if set
const from = [Link]('backup-date-from')?.value;
const to = [Link]('backup-date-to')?.value;
if (from) entries = [Link](e => [Link] && [Link] >= from);
if (to) entries = [Link](e => [Link] && [Link] <= to + 'T23:59:59');
if ([Link] === 0) { [Link] = `<div
class="empty-state"><div class="icon">Ø=ܾ</div><p>No entries found for
selected range.</p></div>`; return; }
[Link] = '';
[Link](e => [Link](makeEntryCard(e, true)));
updateBackupCount();
const info = [Link]('backup-filter-info');
if (info) [Link] = (from || to) ? `Showing ${[Link]} of
${[Link]} entries` : `${[Link]} entries total`;
}
// ---- Last backup times ----
function renderLastBackupTimes() {
const fmt = key => {
const t = [Link](key);
if (!t) return 'Never';
return new Date(t).toLocaleString('en-IN',
{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'});
};
const el = id => [Link](id);
if (el('last-backup-journal')) el('last-backup-journal').textContent =
fmt('last_backup_journal');
if (el('last-backup-eras')) el('last-backup-eras').textContent =
fmt('last_backup_eras');
if (el('last-backup-settings')) el('last-backup-settings').textContent =
fmt('last_backup_settings');
if (el('last-backup-capture')) el('last-backup-capture').textContent =
fmt('last_backup_capture');
}
// ---- Date filter for backup entries ----
function applyBackupQuickRange() {
const val = [Link]('backup-quick-range')?.value;
if (!val || val === 'all') {
if (val === 'all') {
[Link]('backup-date-from').value = '';
[Link]('backup-date-to').value = '';
}
if (!val) return;
} else {
const days = parseInt(val);
const to = new Date();
const from = new Date(); [Link]([Link]() - days);
[Link]('backup-date-from').value =
[Link]().slice(0,10);
[Link]('backup-date-to').value =
[Link]().slice(0,10);
}
renderBackupList();
}
function applyBackupDateFilter() { renderBackupList(); }
function clearBackupFilter() {
[Link]('backup-date-from').value = '';
[Link]('backup-date-to').value = '';
[Link]('backup-quick-range').value = '';
renderBackupList();
}
// ---- Export filtered entries ----
function exportFiltered() {
const from = [Link]('backup-date-from')?.value;
const to = [Link]('backup-date-to')?.value;
let entries = [Link];
if (from) entries = [Link](e => [Link] && [Link] >= from);
if (to) entries = [Link](e => [Link] && [Link] <= to + 'T23:59:59');
if (![Link]) { showToast('& þ No entries in selected range!');
return; }
const slug = 'journal_filtered_' + new Date().toISOString().slice(0,10);
downloadFile([Link](entries, null, 2), slug + '.json',
'application/json');
[Link]('last_backup_journal', new Date().toISOString());
renderLastBackupTimes();
showToast('' Exported ' + [Link] + ' entries!');
}
// ---- Eras backup ----
function exportErasBackup() {
const eras = [Link] || [];
downloadFile([Link]({ eras, exportedAt: new
Date().toISOString() }, null, 2), 'eras_backup_' + new
Date().toISOString().slice(0,10) + '.json', 'application/json');
[Link]('last_backup_eras', new Date().toISOString());
renderLastBackupTimes();
showToast('' Eras exported!');
}
function importErasBackup(event) {
const file = [Link][0]; if (!file) return;
const reader = new FileReader();
[Link] = e => {
try {
const data = [Link]([Link]);
const eras = [Link] || data;
if () { showToast(''L Invalid eras file!'); return; }
if (!confirm(`Import ${[Link]} eras? This will replace existing
eras.`)) return;
[Link] = eras; saveState();
renderEraTimeline();
showToast('' Eras imported!');
} catch { showToast(''L Could not read file!'); }
};
[Link](file);
[Link] = '';
}
// ---- Settings backup ----
function exportSettingsBackup() {
const data = { settings: [Link], exportedAt: new
Date().toISOString() };
downloadFile([Link](data, null, 2), 'settings_backup_' + new
Date().toISOString().slice(0,10) + '.json', 'application/json');
[Link]('last_backup_settings', new Date().toISOString());
renderLastBackupTimes();
showToast('' Settings exported!');
}
function importSettingsBackup(event) {
const file = [Link][0]; if (!file) return;
const reader = new FileReader();
[Link] = e => {
try {
const data = [Link]([Link]);
const settings = [Link] || data;
if (!settings || typeof settings !== 'object') { showToast(''L Invalid
settings file!'); return; }
if (!confirm('Import settings? Current settings will be overwritten.'))
return;
[Link] = { ...[Link], ...settings };
saveState(); refreshPage();
showToast('' Settings imported!');
} catch { showToast(''L Could not read file!'); }
};
[Link](file);
[Link] = '';
}
// ---- Capture backup ----
function exportCaptureBackup() {
const items = getBDItems();
const cats = getBDCategories();
const data = { items, categories: cats, exportedAt: new
Date().toISOString() };
downloadFile([Link](data, null, 2), 'capture_backup_' + new
Date().toISOString().slice(0,10) + '.json', 'application/json');
[Link]('last_backup_capture', new Date().toISOString());
renderLastBackupTimes();
showToast('' Capture backup exported!');
}
function importCaptureBackup(event) {
const file = [Link][0]; if (!file) return;
const reader = new FileReader();
[Link] = e => {
try {
const data = [Link]([Link]);
const items = [Link] || data;
const cats = [Link];
if () { showToast(''L Invalid capture file!'); return; }
if (!confirm(`Import ${[Link]} capture items? This will replace
existing items.`)) return;
saveBDItems(items);
if (cats && [Link](cats)) saveBDCategoriesLocal(cats);
renderBDList();
showToast('' Capture data imported!');
} catch { showToast(''L Could not read file!'); }
};
[Link](file);
[Link] = '';
}
function updateBackupCount() {
const checked =
[Link]('.backup-checkbox:checked').length;
[Link]('backup-selected-count').textContent =
checked ? checked + ' selected' : '';
}
[Link]('change', e => { if
([Link]('backup-checkbox')) updateBackupCount();
});
function selectAllBackup() {
[Link]('.backup-checkbox').forEach(c =>
[Link]=true); updateBackupCount(); }
function deselectAllBackup() {
[Link]('.backup-checkbox').forEach(c =>
[Link]=false); updateBackupCount(); }
function getSelectedIds() {
return
[...[Link]('.backup-checkbox:checked')].map(c =>
[Link]);
}
// =====================================
// EXPORT / IMPORT
// =====================================
function entryToTxt(entry) {
const date = new Date([Link]).toLocaleString('en-US', {
dateStyle:'full', timeStyle:'medium' });
let out = `TITLE: ${[Link] || 'Untitled'}\n`;
out += `DATE: ${date}\n`;
out += `DATE_ISO: ${[Link] || new Date().toISOString()}\n`;
out += `MOOD: ${[Link] || 'Not set'}\n`;
out += `CATEGORY: ${[Link] || 'None'}\n`;
out += `TAGS: ${([Link]||[]).map(t=>'#'+t).join(' ') || 'None'}\n`;
out += `WORDS: ${[Link] || 0}\n`;
out += `WRITING TIME: ${formatTime([Link]||0)}\n`;
if ([Link] && [Link]([Link]).length > 0) {
out += '\n--- CUSTOM QUESTIONS ---\n';
for (const [q, a] of [Link]([Link])) {
if (a) out += `Q: ${q}\nA: ${a}\n`;
}
}
out += '\n--- ENTRY ---\n';
out += ([Link] || '') + '\n';
out += '\n' + '='.repeat(60) + '\n';
return out;
}
function downloadFile(content, filename, type) {
const blob = new Blob([content], { type });
const a = [Link]('a');
[Link] = [Link](blob);
[Link] = filename;
[Link]();
[Link]([Link]);
}
function downloadEntry(entry, fmt) {
if (!entry) return;
const slug = ([Link] ||
'entry').replace(/[^a-z0-9]/gi,'_').toLowerCase().slice(0,40);
downloadFile([Link](entry, null, 2), slug+'.json',
'application/json');
}
function exportAll(fmt) {
const slug = 'journal_all_' + new Date().toISOString().slice(0,10);
downloadFile([Link]([Link], null, 2), slug+'.json',
'application/json');
[Link]('last_backup_journal', new Date().toISOString());
renderLastBackupTimes();
showToast('' Exported ' + [Link] + ' entries!');
}
function exportSelected(fmt) {
const ids = getSelectedIds();
if (![Link]) { showToast('& þ No entries selected!'); return; }
const sel = [Link](e => [Link]([Link]));
const slug = 'journal_selected_' + new Date().toISOString().slice(0,10);
downloadFile([Link](sel, null, 2), slug+'.json',
'application/json');
[Link]('last_backup_journal', new Date().toISOString());
renderLastBackupTimes();
showToast('' Exported ' + [Link] + ' entries!');
}
async function exportZip() {
if (typeof JSZip === 'undefined') { showToast('& þ JSZip not loaded');
return; }
const ids = getSelectedIds();
const entries = [Link] ? [Link](e => [Link]([Link])) :
[Link];
const zip = new JSZip();
[Link](entry => {
const slug = ([Link] ||
'entry').replace(/[^a-z0-9]/gi,'_').toLowerCase().slice(0,40);
[Link](slug + '_' + [Link] + '.json', [Link](entry, null, 2));
});
const blob = await [Link]({ type: 'blob' });
const a = [Link]('a');
[Link] = [Link](blob);
[Link] = 'journal_backup_' + new Date().toISOString().slice(0,10)
+ '.zip';
[Link]();
showToast('' ZIP created with ' + [Link] + ' entries!');
}
function importFile(event, fmt) {
const files = [Link]([Link]);
if (![Link]) return;
let totalImported = 0;
let filesProcessed = 0;
const allNew = [];
[Link](file => {
const reader = new FileReader();
[Link] = e => {
try {
let imported = [];
if (fmt === 'json') {
const data = [Link]([Link]);
imported = [Link](data) ? data : [data];
} else {
const text = [Link];
// Split multiple entries by separator
const parts = [Link](/={60,}/);
[Link](part => {
if (![Link]()) return;
// Helper to extract field value
const get = (key) => {
const m = [Link](new RegExp(key + ': (.+)'));
return m ? m[1].trim() : '';
};
// Parse writing time — handles "5m 30s", "45s", "2m", "05:30"
formats
const parseTime = (str) => {
if (!str || str === '—') return 0;
// "MM:SS" format
const colonFmt = [Link](/^(\d+):(\d+)$/);
if (colonFmt) return parseInt(colonFmt[1]) * 60 +
parseInt(colonFmt[2]);
// "5m 30s" or "5m" or "30s" format
const mins = [Link](/(\d+)m/);
const secs = [Link](/(\d+)s/);
return (mins ? parseInt(mins[1]) * 60 : 0) + (secs ?
parseInt(secs[1]) : 0);
};
// Parse tags "#tag1 #tag2" !’ ['tag1', 'tag2']
const parseTags = (str) => {
if (!str || str === 'None') return [];
return [Link](/#(\w+)/g)?.map(t => [Link](1)) || [];
};
// Parse date string back to ISO
const parseDate = (str) => {
if (!str) return new Date().toISOString();
try {
const d = new Date(str);
// Agar valid date hai toh use karo
if (!isNaN([Link]())) return [Link]();
} catch {}
return new Date().toISOString();
};
// Parse custom questions section
const cqAnswers = {};
const cqSection = [Link]('--- CUSTOM QUESTIONS
---')[1]?.split('--- ENTRY ---')[0] || '';
const cqLines = [Link]('\n');
let currentQ = '';
[Link](line => {
const qm = [Link](/^Q: (.+)/);
const am = [Link](/^A: (.+)/);
if (qm) currentQ = qm[1].trim();
else if (am && currentQ) { cqAnswers[currentQ] = am[1].trim();
currentQ = ''; }
});
const rawText = ([Link]('--- ENTRY ---')[1] || '').trim();
const mood = get('MOOD');
const category = get('CATEGORY');
const writingTimeStr = get('WRITING TIME');
const wordsStr = get('WORDS');
const dateStr = get('DATE_ISO') || get('DATE');
const entry = {
id: genId(),
title: get('TITLE') || 'Imported Entry',
date: parseDate(dateStr),
text: rawText,
mood: mood === 'Not set' ? '' : mood,
category: category === 'None' ? '' : category,
tags: parseTags(get('TAGS')),
wordCount: parseInt(wordsStr) ||
[Link]().split(/ /).filter(Boolean).length,
writingTime: parseTime(writingTimeStr),
cqAnswers
};
[Link](entry);
});
}
[Link](...imported);
} catch(err) {
showToast(''L ' + [Link] + ' read nahi hua!');
}
filesProcessed++;
if (filesProcessed === [Link]) {
// Avoid duplicates by id
const existingIds = new Set([Link](e => [Link]));
const newOnes = [Link](e => );
[Link] = [...newOnes, ...[Link]];
saveState(); refreshPage();
showToast('' ' + [Link] + ' entr' +
([Link]!==1?'ies':'y') + ' import hui (' + [Link] + ' file' +
([Link]>1?'s':'') + ')!');
}
};
[Link](file);
});
[Link] = '';
}
// =====================================
// THEME
// =====================================
function applyTheme() {
const isDark = [Link];
[Link]('dark', isDark);
const pill = [Link]('dark-toggle');
if (pill) [Link]('on', isDark);
}
function toggleTheme() {
[Link] = ![Link];
applyTheme();
saveState();
}
function applyFontSize() {
[Link]('textarea').forEach(t => [Link] =
[Link]);
const sel = [Link]('font-size-sel');
if (sel) [Link] = [Link];
}
function changeFontSize() {
[Link] =
[Link]('font-size-sel').value;
applyFontSize();
saveState();
}
// =====================================
// DATA MANAGEMENT
// =====================================
function clearAllData() {
if (!confirm('& þ This will permanently delete ALL entries. Are you
sure?')) return;
if (!confirm('Last chance! Really delete everything?')) return;
[Link] = [];
saveState(); refreshPage(); showToast('All entries deleted.');
}
// =====================================
// MODAL HELPERS
// =====================================
function showModal(id) {
[Link](id).[Link]('open'); }
function closeModal(id) {
[Link](id).[Link]('open'); }
[Link]('.modal-overlay').forEach(o => {
[Link]('click', e => { if ([Link] === o) closeModal([Link]);
});
});
// =====================================
// TOAST
// =====================================
function showToast(msg) {
const t = [Link]('toast');
[Link] = msg; [Link]('show');
setTimeout(() => [Link]('show'), 2800);
}
// =====================================
// KEYBOARD SHORTCUTS
// =====================================
[Link]('keydown', e => {
if ([Link] === 'Escape') {
[Link]('.[Link]').forEach(m =>
[Link]('open'));
}
});
// =====================================
// QUESTIONS SESSION
// =====================================
let qTimers = {}; // { index: { seconds, interval, started, done } }
let qSessionActive = false;
function initQuestionsPage() {
const qs = [Link];
const noQ = [Link]('q-no-questions');
const container = [Link]('q-cards-container');
const saveRow = [Link]('q-save-row');
const summary = [Link]('q-session-summary');
if (!container) return;
if (!qs || [Link] === 0) {
if (noQ) [Link] = 'block';
[Link] = '';
if (saveRow) [Link] = 'none';
return;
}
if (noQ) [Link] = 'none';
// If session already rendered, don't re-render
if (qSessionActive) return;
// Reset
qTimers = {};
if (summary) { [Link] = 'none'; [Link] =
''; }
[Link] = '';
if (saveRow) [Link] = 'none';
[Link]((q, i) => {
qTimers[i] = { seconds: 0, interval: null, started: false, done: false };
const card = [Link]('div');
[Link] = 'q-session-card';
[Link] = 'q-card-' + i;
[Link] = `
<div class="q-session-header">
<div class="q-number">${i+1}</div>
<div class="q-text">${q}</div>
<div class="q-timer-badge" id="q-badge-${i}">
<div class="q-timer-dot"></div>
<span id="q-time-${i}">00:00</span>
</div>
</div>
<textarea
id="q-ans-${i}"
placeholder="Yahan apna jawab likhein…"
style="min-height:90px;width:100%;"
onfocus="startQTimer(${i})"
onblur="pauseQTimer(${i})"
oninput="onQInput(${i})"
></textarea>
<div class="q-controls">
<button class="btn btn-outline btn-sm" onclick="toggleQTimer(${i})"
id="q-btn-${i}">%¶ Start Timer</button>
<button class="btn btn-ghost btn-sm" onclick="resetQTimer(${i})">!º
Reset</button>
<span class="q-status-line" id="q-status-${i}">Timer shuru nahi
hua</span>
</div>
`;
[Link](card);
});
if (saveRow) [Link] = 'flex';
qSessionActive = true;
}
function startNewSession() {
qSessionActive = false;
// Stop all running timers
[Link](qTimers).forEach(t => { if ([Link])
clearInterval([Link]); });
qTimers = {};
initQuestionsPage();
}
function resetSession() {
if (!confirm('Saari answers aur timers reset ho jaayenge?')) return;
qSessionActive = false;
[Link](qTimers).forEach(t => { if ([Link])
clearInterval([Link]); });
qTimers = {};
initQuestionsPage();
}
function startQTimer(i) {
const t = qTimers[i];
if (!t || [Link]) return;
if ([Link]) return; // already running
[Link] = true;
[Link] = setInterval(() => {
[Link]++;
updateQTimerDisplay(i);
}, 1000);
updateQBadge(i, 'running');
[Link]('q-card-' + i)?.[Link]('active-q');
const btn = [Link]('q-btn-' + i);
if (btn) [Link] = '#ø Pause';
const status = [Link]('q-status-' + i);
if (status) [Link] = 'Likh rahe ho… timer chal raha hai #ñ';
}
function pauseQTimer(i) {
const t = qTimers[i];
if (!t || ![Link]) return;
clearInterval([Link]);
[Link] = null;
updateQBadge(i, [Link] ? 'done' : 'paused');
[Link]('q-card-' + i)?.[Link]('active-q');
const btn = [Link]('q-btn-' + i);
if (btn) [Link] = '%¶ Resume';
const status = [Link]('q-status-' + i);
if (status) [Link] = 'Paused — ' + formatTime([Link]) + '
likha';
}
function toggleQTimer(i) {
const t = qTimers[i];
if (!t) return;
if ([Link]) {
pauseQTimer(i);
} else {
startQTimer(i);
// also focus the textarea
[Link]('q-ans-' + i)?.focus();
}
}
function resetQTimer(i) {
const t = qTimers[i];
if (!t) return;
if ([Link]) clearInterval([Link]);
[Link] = 0; [Link] = null; [Link] = false; [Link] = false;
updateQTimerDisplay(i);
updateQBadge(i, 'idle');
[Link]('q-card-' + i)?.[Link]('active-q');
const btn = [Link]('q-btn-' + i);
if (btn) [Link] = '%¶ Start Timer';
const status = [Link]('q-status-' + i);
if (status) [Link] = 'Timer shuru nahi hua';
}
function onQInput(i) {
const t = qTimers[i];
if (!t) return;
if (![Link] && ![Link]) {
startQTimer(i);
}
}
function updateQTimerDisplay(i) {
const t = qTimers[i];
const el = [Link]('q-time-' + i);
if (el) {
const m = String([Link]([Link] / 60)).padStart(2, '0');
const s = String([Link] % 60).padStart(2, '0');
[Link] = m + ':' + s;
}
}
function updateQBadge(i, state_str) {
const badge = [Link]('q-badge-' + i);
if (!badge) return;
[Link]('running', 'done');
if (state_str === 'running') [Link]('running');
else if (state_str === 'done') [Link]('done');
}
function saveQuestionSession() {
const qs = [Link];
if (!qs || [Link] === 0) return;
// Stop all timers
[Link](qTimers).forEach(i => {
const t = qTimers[i];
if ([Link]) { clearInterval([Link]); [Link] = null; }
[Link] = true;
updateQBadge(i, 'done');
[Link]('q-card-' + i)?.[Link]('active-q');
const btn = [Link]('q-btn-' + i);
if (btn) [Link] = '' Done';
const status = [Link]('q-status-' + i);
if (status) [Link] = '' Saved — Total time: ' +
formatTime([Link]);
});
const cqAnswers = {};
const cqTimes = {};
let totalSecs = 0;
[Link]((q, i) => {
const ans = [Link]('q-ans-' + i)?.[Link]() || '';
const secs = qTimers[i]?.seconds || 0;
cqAnswers[q] = ans;
cqTimes[q] = secs;
totalSecs += secs;
});
const wordCount = [Link](cqAnswers).join('
').trim().split(/ /).filter(Boolean).length;
const entry = {
id: genId(),
title: 'Q&A Session — ' + new Date().toLocaleDateString('en-US', {
month: 'short', day: 'numeric', year: 'numeric' }),
text: [Link](cqAnswers).map(([q,a]) => 'Q: ' + q + '\nA: ' + (a ||
'(no answer)')).join('\n\n'),
mood: '',
category: 'Q&A Session',
tags: ['questions', 'reflection'],
cqAnswers,
cqTimes,
wordCount,
writingTime: totalSecs,
date: new Date().toISOString(),
created: [Link](),
isQSession: true
};
[Link](entry);
saveState();
// Flask se local + rclone save
saveEntryToFlask(entry, 'json', true);
// Show summary
const summary = [Link]('q-session-summary');
if (summary) {
let rows = [Link]((q, i) => {
const secs = qTimers[i]?.seconds || 0;
const ans = cqAnswers[q];
return `<div class="summary-row"><span>${[Link] > 40 ?
[Link](0,40)+'…' : q}</span><span><b>${formatTime(secs)}</b>${ans
? '' : ' <span style="color:var(--text2);font-size:0.72rem;">(skipped)</spa
n>'}</span></div>`;
}).join('');
rows += `<div class="summary-row"><span>Total
Time</span><span>${formatTime(totalSecs)}</span></div>`;
[Link] = `<h3>' Session Saved!</h3>${rows}`;
[Link] = 'block';
[Link]({ behavior: 'smooth' });
}
pendingSaveEntry = entry;
selectedSaveFmt = 'json';
selectFmt('json');
showModal('save-local-modal');
showToast('' Q&A Session save ho gayi!');
}
// =====================================
// STAR / FAVOURITE
// =====================================
function toggleStar(id) {
const entry = getEntry(id);
if (!entry) return;
[Link] = ![Link];
saveState();
refreshPage();
showToast([Link] ? '+P Starred!' : 'Star removed');
}
function toggleStarCurrent() {
if (!currentDetailId) return;
toggleStar(currentDetailId);
const entry = getEntry(currentDetailId);
const starBtn = [Link]('detail-star-btn');
if (starBtn && entry) {
[Link] = [Link] ? '& Starred' : '& Favourite';
[Link] = [Link] ? '#f5c518' : '';
[Link] = [Link] ? '#f5c518' : '';
}
}
function renderFavouritesList() {
const container = [Link]('favourites-list');
if (!container) return;
const favs = [Link](e => [Link]);
if ([Link] === 0) {
[Link] = `<div class="empty-state"><div
class="icon">+P</div><p>Koi favourite entry nahi hai abhi.<br>Entry par
& click karo favourite karne ke liye.</p></div>`;
return;
}
[Link] = '';
[Link](entry => [Link](makeEntryCard(entry,
false)));
}
// =====================================
// ERA SYSTEM
// =====================================
const ERA_COLORS_DEFAULT = ['#e74c3c','#e67e22','#f1c40f','#2ecc
71','#1abc9c','#3498db','#9b59b6','#e91e63','#00bcd4','#ff5722','#8bc34
a','#607d8b'];
function getEraColors() {
try { return [Link]([Link]('era_colors') || 'null') ||
[...ERA_COLORS_DEFAULT]; } catch { return
[...ERA_COLORS_DEFAULT]; }
}
function saveEraColors(colors) { [Link]('era_colors',
[Link](colors)); }
let eraViewYear = new Date().getFullYear();
let eraViewMonth = new Date().getMonth(); // 0-based
let eraStarFilter = 'all';
let newEraColor = getEraColors()[0];
function setEraCustomColor(val) { newEraColor = val; }
function addEraCustomColor() {
const val = [Link]('era-custom-color')?.value;
if (!val) return;
const colors = getEraColors();
if () { [Link](val); saveEraColors(colors); }
newEraColor = val;
renderEraColorPicker();
showToast('' Color added to palette!');
}
function deleteEraColor(color) {
const colors = getEraColors().filter(c => c !== color);
if ([Link] === 0) { showToast('& þ At least one color required!');
return; }
saveEraColors(colors);
if (newEraColor === color) newEraColor = colors[0];
renderEraColorPicker();
showToast('Ø=ÝÑ Color removed!');
}
function toggleEraForm() {
const f = [Link]('era-add-form');
if (!f) return;
const visible = [Link] !== 'none';
[Link] = visible ? 'none' : 'block';
if (!visible) renderEraColorPicker();
}
function renderEraColorPicker() {
const wrap = [Link]('era-color-picker');
if (!wrap) return;
const colors = getEraColors();
[Link] = '';
[Link](c => {
const container = [Link]('div');
[Link] = 'position:relative;display:inline-block;';
const el = [Link]('div');
[Link] = 'era-color-opt' + (c === newEraColor ? ' selected' : '');
[Link] = c;
[Link] = () => { newEraColor = c;
[Link]('era-custom-color').value = c;
renderEraColorPicker(); };
[Link] = c;
// Delete button (small x on hover)
const del = [Link]('button');
[Link] = '×';
[Link] = 'position:absolute;top:-5px;right:-5px;width:14px;he
ight:14px;border-radius:50%;background:#e74c3c;color:white;border:no
ne;cursor:pointer;font-size:9px;line-height:1;padding:0;display:flex;align-i
tems:center;justify-content:center;opacity:0;transition:opacity 0.15s;';
[Link] = (e) => { [Link](); deleteEraColor(c); };
[Link]('mouseenter', () => [Link] = '1');
[Link]('mouseleave', () => [Link] =
'0');
[Link](el);
[Link](del);
[Link](container);
});
}
function flaskSaveEras() {
fetch(`${FLASK_API}/eras`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: [Link]({ eras: [Link] || [] })
}).catch(() => {});
}
function saveEra() {
const name =
[Link]('era-name-input')?.[Link]();
const start = [Link]('era-start-input')?.value;
const end = [Link]('era-end-input')?.value;
if (!name || !start) { showToast('& þ Name aur start date zaroori hai!');
return; }
if (![Link]) [Link] = [];
[Link]({ id: genId(), name, start, end: end || null,
color: newEraColor });
saveState();
flaskSaveEras();
[Link]('era-name-input').value = '';
[Link]('era-start-input').value = '';
[Link]('era-end-input').value = '';
toggleEraForm();
renderEraTimeline();
showToast('' Era save ho gaya!');
}
function deleteEra(id) {
if (!confirm('Yeh era delete kar dein?')) return;
[Link] = ([Link] || []).filter(e => [Link] !== id);
saveState();
flaskSaveEras();
renderEraTimeline();
}
function getEraForDate(dateStr) {
const eras = [Link] || [];
const d = new Date(dateStr);
for (const era of eras) {
const s = new Date([Link]);
const e = [Link] ? new Date([Link]) : new Date('9999-12-31');
if (d >= s && d <= e) return era;
}
return null;
}
function eraMonthNav(dir) {
eraViewMonth += dir;
if (eraViewMonth > 11) { eraViewMonth = 0; eraViewYear++; }
if (eraViewMonth < 0) { eraViewMonth = 11; eraViewYear--; }
if (eraCurrentView === 'grid') { updateEraMonthLabel();
renderEraGrid(); }
else renderEraTimeline();
}
function eraGoToday() {
eraViewYear = new Date().getFullYear();
eraViewMonth = new Date().getMonth();
if (eraCurrentView === 'grid') { updateEraMonthLabel();
renderEraGrid(); }
else renderEraTimeline();
}
function updateEraMonthLabel() {
const monthLabel = [Link]('era-month-label');
if (monthLabel) [Link] = new Date(eraViewYear,
eraViewMonth, 1).toLocaleDateString('en-US', { month: 'long', year:
'numeric' });
}
function setEraStarFilter(f) {
eraStarFilter = f;
[Link]('era-filter-all').[Link]('active', f ===
'all');
[Link]('era-filter-starred').[Link]('active', f
=== 'starred');
if (eraCurrentView === 'grid') renderEraGrid();
else renderEraTimeline();
}
function renderEraTimeline() {
const eras = [Link] || [];
// Month label
updateEraMonthLabel();
// Legend
const legend = [Link]('era-legend');
if (legend) {
if ([Link] === 0) {
[Link] = '<span
style="font-size:0.8rem;color:var(--text2);">Koi era nahi hai abhi. "+ New
Era" se banao.</span>';
} else {
[Link] = [Link](era => `
<div class="era-legend-chip" style="background:${[Link]}22;colo
r:${[Link]};border-color:${[Link]}44;">
<div style="width:8px;height:8px;border-radius:50%;background:$
{[Link]};flex-shrink:0;"></div>
${[Link]}
<button onclick="deleteEra('${[Link]}')" style="background:none;b
order:none;cursor:pointer;color:${[Link]};font-size:0.75rem;padding:0
0 0 4px;line-height:1;">'</button>
</div>
`).join('');
}
}
// Build days of month
const container = [Link]('era-timeline-container');
if (!container) return;
[Link] = '';
const daysInMonth = new Date(eraViewYear, eraViewMonth + 1,
0).getDate();
const today = new Date();
const todayStr = [Link]().slice(0, 10);
// Build entry map by date
const entryMap = {};
[Link](entry => {
const d = [Link] ? [Link](0, 10) : null;
if (!d) return;
if (eraStarFilter === 'starred' && ![Link]) return;
if (!entryMap[d]) entryMap[d] = [];
entryMap[d].push(entry);
});
for (let day = 1; day <= daysInMonth; day++) {
const dateStr = `${eraViewYear}-${String(eraViewMonth +
1).padStart(2,'0')}-${String(day).padStart(2,'0')}`;
const era = getEraForDate(dateStr);
const entries = entryMap[dateStr] || [];
const isToday = dateStr === todayStr;
const dayName = new Date(dateStr).toLocaleDateString('en-US', {
weekday: 'short' });
const row = [Link]('div');
[Link] = 'era-day-row';
const dot = [Link]('div');
[Link] = 'era-day-dot' + ([Link] > 0 ? ' has-entry' : '');
[Link](dot);
const card = [Link]('div');
[Link] = 'era-day-card';
if (isToday) [Link] = 'var(--accent)';
if (isToday) [Link] = '0 0 0 2px var(--accent-light)';
// Date
const dateDiv = [Link]('div');
[Link] = 'era-day-date';
[Link] = `<div style="font-weight:${isToday?'800':'400'};c
olor:${isToday?'var(--accent)':'var(--text2)'};">${day}</div><div
style="font-size:0.68rem;">${dayName}</div>`;
[Link](dateDiv);
// Era band
const band = [Link]('div');
[Link] = 'era-day-era-band';
[Link] = era ? [Link] : 'var(--border)';
[Link] = era ? '1' : '0.3';
[Link] = era ? [Link] : '';
[Link](band);
// Entries
const entriesDiv = [Link]('div');
[Link] = 'era-day-entries';
if ([Link] === 0) {
const empty = [Link]('div');
[Link] = 'era-day-empty';
[Link] = era ? [Link] : '—';
[Link](empty);
} else {
[Link](e => {
const t = [Link]('div');
[Link] = 'era-day-entry-title';
[Link] = ([Link] ? '<span class="star-inline">&</span>' : '')
+ ([Link] || 'Untitled');
[Link] = () => openDetail([Link]);
[Link](t);
});
}
[Link](entriesDiv);
// Era badge
if (era) {
const badge = [Link]('div');
[Link] = 'era-label-badge';
[Link] = [Link] + '22';
[Link] = [Link];
[Link] = [Link];
[Link](badge);
}
[Link](card);
[Link](row);
}
}
// =====================================
// ERA VIEW TOGGLE
// =====================================
let eraCurrentView = 'timeline'; // 'timeline' or 'grid'
function setEraView(view) {
eraCurrentView = view;
[Link]('era-timeline-container').[Link] = view
=== 'timeline' ? '' : 'none';
[Link]('era-grid-container').[Link] = view
=== 'grid' ? '' : 'none';
[Link]('era-view-timeline-btn').[Link]('acti
ve-view-btn', view === 'timeline');
[Link]('era-view-grid-btn').[Link]('active-v
iew-btn', view === 'grid');
if (view === 'timeline') renderEraTimeline();
else renderEraGrid();
}
// =====================================
// ERA GRID VIEW
// =====================================
function renderEraGrid() {
const container = [Link]('era-grid-container');
if (!container) return;
[Link] = '';
const daysInMonth = new Date(eraViewYear, eraViewMonth + 1,
0).getDate();
const firstDow = new Date(eraViewYear, eraViewMonth, 1).getDay(); //
0=Sun
const todayStr = new Date().toISOString().slice(0, 10);
// Build entry map
const entryMap = {};
[Link](e => {
const d = [Link] ? [Link](0, 10) : null;
if (!d) return;
if (eraStarFilter === 'starred' && ![Link]) return;
if (!entryMap[d]) entryMap[d] = [];
entryMap[d].push(e);
});
// Day-of-week headers
const headerDiv = [Link]('div');
[Link] = 'era-grid-header';
['Sun','Mon','Tue','Wed','Thu','Fri','Sat'].forEach(d => {
const h = [Link]('div');
[Link] = 'era-grid-dow';
[Link] = d;
[Link](h);
});
[Link](headerDiv);
const gridDiv = [Link]('div');
[Link] = 'era-grid-wrap';
// Empty cells before first day
for (let i = 0; i < firstDow; i++) {
const empty = [Link]('div');
[Link] = 'era-grid-cell empty';
[Link](empty);
}
for (let day = 1; day <= daysInMonth; day++) {
const dateStr = `${eraViewYear}-${String(eraViewMonth+1).padStart(
2,'0')}-${String(day).padStart(2,'0')}`;
const era = getEraForDate(dateStr);
const entries = entryMap[dateStr] || [];
const isToday = dateStr === todayStr;
const cell = [Link]('div');
[Link] = 'era-grid-cell' + (isToday ? ' today' : '');
// Era background color
if (era) {
[Link] = [Link] + '33';
[Link] = [Link] + '66';
} else {
[Link] = 'var(--surface)';
[Link] = 'var(--border)';
}
// Day number
const num = [Link]('div');
[Link] = 'era-grid-day-num';
[Link] = day;
[Link] = isToday ? 'var(--accent)' : (era ? [Link] :
'var(--text)');
[Link](num);
// Entry dot
if ([Link] > 0) {
const dot = [Link]('div');
[Link] = 'era-grid-dot';
[Link] = era ? [Link] : 'var(--accent)';
[Link](dot);
}
// Era bar at bottom
if (era) {
const bar = [Link]('div');
[Link] = 'era-grid-era-bar';
[Link] = [Link];
[Link] = [Link];
[Link](bar);
}
// Click: if entries exist open first, else show date toast
if ([Link] === 1) {
[Link] = () => openDetail(entries[0].id);
} else if ([Link] > 1) {
[Link] = () => {
// show mini picker — open first entry for now, toast tells count
showToast(`${[Link]} entries on this day`);
openDetail(entries[0].id);
};
// badge
const badge = [Link]('div');
[Link] = 'position:absolute;top:2px;right:3px;font-size:0
.6rem;font-weight:800;color:white;background:var(--accent);border-radiu
s:8px;padding:0 4px;line-height:1.4;z-index:2;';
[Link] = [Link];
[Link](badge);
} else {
[Link] = era ? [Link] : '';
}
[Link](cell);
}
[Link](gridDiv);
// Era legend below grid
const eras = [Link] || [];
if ([Link] > 0) {
const leg = [Link]('div');
[Link] =
'display:flex;flex-wrap:wrap;gap:6px;margin-top:14px;';
[Link](era => {
const chip = [Link]('div');
[Link] =
`display:flex;align-items:center;gap:5px;padding:3px 10px;border-radius:
12px;font-size:0.72rem;font-weight:700;background:${[Link]}22;color:
${[Link]};`;
[Link] = `<div style="width:8px;height:8px;border-radius:50
%;background:${[Link]};"></div>${[Link]}`;
[Link](chip);
});
[Link](leg);
}
}
// =====================================
// ERA EXPORT / IMPORT
// =====================================
function exportEras() {
const eras = [Link] || [];
if ([Link] === 0) { showToast('& þ Koi era nahi hai export karne ke
liye!'); return; }
const data = [Link]({ eras, exportedAt: new
Date().toISOString() }, null, 2);
const blob = new Blob([data], { type: 'application/json' });
const a = [Link]('a');
[Link] = [Link](blob);
[Link] = 'my_eras_' + new Date().toISOString().slice(0,10) +
'.json';
[Link]();
[Link]([Link]);
showToast('' Eras export ho gaye!');
}
function importEras(event) {
const file = [Link][0];
if (!file) return;
const reader = new FileReader();
[Link] = function(e) {
try {
const data = [Link]([Link]);
const incoming = [Link] || data; // support both formats
if () { showToast(''L Invalid file format!');
return; }
// Merge: skip duplicates by id
if (![Link]) [Link] = [];
let added = 0;
[Link](era => {
if () {
[Link](era);
added++;
}
});
saveState();
renderEraTimeline();
if (eraCurrentView === 'grid') renderEraGrid();
showToast(`' ${added} era(s) import ho gaye!`);
} catch(err) {
showToast(''L File read nahi ho saki!');
}
};
[Link](file);
[Link] = ''; // reset input
}
// =====================================
// RCLONE ENTRY SYNC
// =====================================
async function rcloneSyncEntry() {
if (!pendingSaveEntry) return;
showToast('#ó Syncing to Drive via rclone...');
const result = await saveEntryToFlask(pendingSaveEntry,
selectedSaveFmt || 'json', true);
if (result) {
closeModal('save-local-modal');
pendingSaveEntry = null;
}
}
// =====================================
// BRAIN DUMP
// =====================================
const BD_DEFAULT_CATS = [
{id:'video', label:'Ø=Üù Watch Later'},
{id:'idea', label:'Ø=Ü¡ Idea'},
{id:'kaam', label:'' Task'},
{id:'padhai', label:'Ø=ÜÚ Study'},
{id:'baad', label:'#ó Post-Exam'},
{id:'other', label:'Ø=ÝÂ Other'}
];
function getBDCategories() {
try { return [Link]([Link]('bd_categories') || 'null') ||
BD_DEFAULT_CATS; } catch { return BD_DEFAULT_CATS; }
}
function saveBDCategoriesLocal(cats) {
[Link]('bd_categories', [Link](cats));
}
function getBDItems() {
try { return [Link]([Link]('braindump_items') || '[]');
} catch { return []; }
}
function saveBDItems(items) {
[Link]('braindump_items', [Link](items));
// Flask se bhi save karo agar available hai
saveBDToFlask(items);
}
async function saveBDToFlask(items) {
try {
await fetch('[Link] {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: [Link]({items})
});
} catch(e) { /* Flask nahi mila toh ignore */ }
}
async function loadBDFromFlask() {
try {
const res = await fetch('[Link]
if (![Link]) return;
const data = await [Link]();
if ([Link] && [Link] > 0) {
saveBDItems([Link]);
renderBDList();
}
} catch(e) { /* offline mode */ }
}
async function saveBDCategoriesToFlask(cats) {
try {
await fetch('[Link] {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: [Link]({categories: cats})
});
} catch(e) {}
}
async function loadBDCategoriesFromFlask() {
try {
const res = await fetch('[Link]
if (![Link]) return;
const data = await [Link]();
if ([Link] && [Link] > 0) {
saveBDCategoriesLocal([Link]);
renderBDCategorySelect();
renderBDCategoryManager();
}
} catch(e) {}
}
function renderBDCategorySelect() {
const sel = [Link]('bd-category');
if (!sel) return;
const cats = getBDCategories();
[Link] = [Link](c => `<option
value="${[Link]}">${[Link]}</option>`).join('');
}
function renderBDCategoryManager() {
const container = [Link]('bd-cat-list');
if (!container) return;
const cats = getBDCategories();
[Link] = [Link](c => `
<div style="display:flex;align-items:center;gap:6px;background:var(--s
urface2);
padding:5px 10px;border-radius:20px;font-size:0.82rem;">
<span>${[Link]}</span>
<button onclick="deleteBDCategory('${[Link]}')"
style="background:none;border:none;cursor:pointer;color:var(--text2);
font-size:1rem;line-height:1;padding:0 2px;"
title="Delete">×</button>
</div>
`).join('');
}
function addBDCategory() {
const input = [Link]('bd-new-cat-input');
const val = [Link]();
if (!val) { showToast('& þ Please enter something!'); return; }
const cats = getBDCategories();
const newId = 'cat_' + [Link]();
[Link]({id: newId, label: val});
saveBDCategoriesLocal(cats);
saveBDCategoriesToFlask(cats);
[Link] = '';
renderBDCategorySelect();
renderBDCategoryManager();
showToast('' Category added successfully!');
}
function deleteBDCategory(id) {
const defaults = ['video','idea','kaam','padhai','baad','other'];
if ([Link](id)) { showToast('& þ Default categories cannot be
deleted!'); return; }
const cats = getBDCategories().filter(c => [Link] !== id);
saveBDCategoriesLocal(cats);
saveBDCategoriesToFlask(cats);
renderBDCategorySelect();
renderBDCategoryManager();
showToast('Ø=ÝÑ Category removed!');
}
function addBrainDump() {
const text = [Link]('bd-input').[Link]();
if (!text) { showToast('& þ Please enter something!'); return; }
const catId = [Link]('bd-category').value;
const cats = getBDCategories();
const cat = [Link](c => [Link] === catId) || {id: catId, label: catId};
const priority = [Link]('bd-priority').value;
const items = getBDItems();
[Link]({
id: [Link](),
text,
categoryId: [Link],
categoryLabel: [Link],
priority,
done: false,
createdAt: new Date().toISOString()
});
saveBDItems(items);
[Link]('bd-input').value = '';
renderBDList();
showToast('' Logged! Now get back to what matters Ø=Ý%');
}
let bdCurrentFilter = 'all';
function filterBD(filter) {
bdCurrentFilter = filter;
[Link]('.bd-filter-btn').forEach(b =>
[Link]('active-filter'));
const btn = [Link]('bdf-' + filter);
if (btn) [Link]('active-filter');
renderBDList();
}
const priorityColors = { low:'#27ae60', medium:'#f39c12', high:'#e74c3c'
};
const priorityLabels = { low:'Ø=ßâ Low', medium:'Ø=ßá Medium', high:'Ø=Ý4 Hig
};
function renderBDList() {
renderBDCategorySelect();
renderBDCategoryManager();
const items = getBDItems();
const list = [Link]('bd-list');
const stats = [Link]('bd-stats');
if (!list) return;
const pending = [Link](i => ![Link]).length;
const done = [Link](i => [Link]).length;
if (stats) [Link] = `Total: ${[Link]} · Pending:
${pending} · Done: ${done}`;
let filtered = items;
if (bdCurrentFilter === 'pending') filtered = [Link](i => ![Link]);
else if (bdCurrentFilter === 'done') filtered = [Link](i => [Link]);
else if (bdCurrentFilter === 'exam-baad') filtered = [Link](i =>
[Link] === 'baad');
if ([Link] === 0) {
[Link] = `<div
style="text-align:center;padding:40px;color:var(--text2);">
<div style="font-size:2rem;margin-bottom:10px;">Ø>Ýà</div>
<div>${bdCurrentFilter === 'done' ? 'Nothing completed yet!' : 'All
clear! Time to focus. Ø=Ý%'}</div>
</div>`;
return;
}
[Link] = [Link](item => `
<div
style="background:var(--surface);border-radius:12px;padding:14px
16px;margin-bottom:10px;
box-shadow:var(--shadow);border-left:4px solid
${priorityColors[[Link]] || '#ccc'};
opacity:${[Link] ? '0.6' : '1'};transition:opacity 0.2s;">
<div style="display:flex;align-items:flex-start;gap:12px;">
<input type="checkbox" ${[Link] ? 'checked' : ''}
onchange="toggleBDItem(${[Link]})"
style="margin-top:3px;width:18px;height:18px;accent-color:var(--a
ccent);cursor:pointer;flex-shrink:0;">
<div style="flex:1;">
<div style="font-size:0.95rem;font-weight:500;${[Link] ?
'text-decoration:line-through;color:var(--text2);' : ''}">${[Link]}</div>
<div style="display:flex;gap:8px;margin-top:6px;flex-wrap:wrap;">
<span
style="font-size:0.72rem;background:var(--surface2);padding:2px
8px;border-radius:10px;color:var(--text2);">${[Link] ||
[Link] || ''}</span>
<span style="font-size:0.72rem;color:${priorityColors[[Link]
y]};font-weight:600;">${priorityLabels[[Link]]}</span>
<span style="font-size:0.7rem;color:var(--text2);font-family:var(--f
ont-mono);">${new Date([Link]).toLocaleDateString('en-IN',{da
y:'numeric',month:'short',hour:'2-digit',minute:'2-digit'})}</span>
</div>
</div>
<button onclick="deleteBDItem(${[Link]})" class="btn btn-ghost
btn-sm"
style="flex-shrink:0;padding:4px 8px;font-size:1rem;"
title="Delete">Ø=ÝÑ</button>
</div>
</div>
`).join('');
renderBDAnalysis(items);
}
function toggleBDItem(id) {
const items = getBDItems();
const item = [Link](i => [Link] === id);
if (item) {
[Link] = ![Link];
saveBDItems(items);
renderBDList();
if ([Link]) showToast('' Marked complete! Review the rest later
Ø=Þ');
}
}
function deleteBDItem(id) {
const items = getBDItems().filter(i => [Link] !== id);
saveBDItems(items);
renderBDList();
showToast('Ø=ÝÑ Item removed!');
}
function renderBDAnalysis(items) {
const analysisBox = [Link]('bd-analysis');
const analysisContent =
[Link]('bd-analysis-content');
if (!analysisBox || !analysisContent) return;
const doneItems = [Link](i => [Link]);
if ([Link] === 0) { [Link] = 'none'; return;
}
[Link] = 'block';
// Category breakdown of completed
const catMap = {};
[Link](i => { const l = [Link] || [Link] ||
'Other'; catMap[l] = (catMap[l]||0)+1; });
const catRows =
[Link](catMap).sort((a,b)=>b[1]-a[1]).map(([label, val]) => {
const pct = [Link]((val / [Link]) * 100);
return `<div class="bar-row">
<div class="bar-label">${label}</div>
<div class="bar-track"><div class="bar-fill" style="width:${pct}%;bac
kground:linear-gradient(90deg,var(--success),#52c48a);">
<span class="bar-val">${val}</span></div></div>
</div>`;
}).join('');
// Priority breakdown of completed
const priMap = { high: 0, medium: 0, low: 0 };
[Link](i => { if (priMap[[Link]] !== undefined)
priMap[[Link]]++; });
const completionRate = [Link] ? [Link](([Link] /
[Link]) * 100) : 0;
[Link] = `
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax
(140px,1fr));gap:10px;margin-bottom:16px;">
<div class="stat-card" style="padding:12px 14px;">
<div class="stat-value"
style="font-size:1.5rem;color:var(--success)">${[Link]}</div>
<div class="stat-label">Tasks Done</div>
</div>
<div class="stat-card" style="padding:12px 14px;">
<div class="stat-value"
style="font-size:1.5rem;color:var(--accent)">${completionRate}%</div>
<div class="stat-label">Completion Rate</div>
</div>
<div class="stat-card" style="padding:12px 14px;">
<div class="stat-value"
style="font-size:1.5rem;color:#e74c3c">${[Link]}</div>
<div class="stat-label">High Priority Done</div>
</div>
</div>
<p style="font-size:0.78rem;font-weight:700;text-transform:uppercase;
letter-spacing:0.07em;color:var(--text2);margin-bottom:10px;">Complete
d by Category</p>
<div class="bar-chart">${catRows}</div>
<div style="margin-top:14px;display:flex;gap:10px;flex-wrap:wrap;">
<span style="font-size:0.8rem;background:rgba(231,76,60,0.1);color
:#e74c3c;padding:4px 12px;border-radius:20px;font-weight:600;">Ø=Ý4
High: ${[Link]}</span>
<span style="font-size:0.8rem;background:rgba(243,156,18,0.1);col
or:#f39c12;padding:4px 12px;border-radius:20px;font-weight:600;">Ø=ßá
Medium: ${[Link]}</span>
<span style="font-size:0.8rem;background:rgba(39,174,96,0.1);color
:var(--success);padding:4px
12px;border-radius:20px;font-weight:600;">Ø=ßâ Low:
${[Link]}</span>
</div>
`;
}
// =====================================
// OTHER MODULES
// =====================================
let otherFileContent = '';
let otherFileOriginalName = '';
let otherSelectedIcon = 'Ø>Ýé';
const OTHER_ICONS = [
'Ø>Ýé','Ø=ÜÊ','Ø=ܰ','Ø=ÜÅ',''','Ø<ßËþ','Ø=ÜÚ','Ø<߯','Ø=Ü¡','Ø=ÝÒþ',
'Ø=Ý'','Ø<ß®','Ø<ß','Ø=ÜÈ','Ø>Ýî','Ø<ߨ','Ø=Ý','Ø=Üæ','Ø<ß1','#ñþ',
'Ø<ßµ','Ø<ßN','Ø=ÜŠ','Ø=Þ—',''þ','Ø<ßà','Ø=Üd','Ø=Ü÷','Ø>Ýà','&¡'
];
function initIconPicker() {
const grid = [Link]('other-icon-grid');
if (!grid || [Link] > 0) return;
[Link] = OTHER_ICONS.map(ic => `
<span onclick="selectOtherIcon('${ic}')"
style="font-size:1.5rem;padding:6px
8px;border-radius:8px;cursor:pointer;
background:var(--surface);border:1.5px solid var(--border);
transition:transform 0.15s,border-color 0.15s;"
onmouseenter="[Link]='scale(1.2)';[Link]
or='var(--accent)'"
onmouseleave="[Link]='scale(1)';[Link]
='var(--border)'"
title="${ic}">${ic}</span>
`).join('');
}
function toggleIconPicker() {
initIconPicker();
const p = [Link]('other-icon-picker');
[Link] = [Link] === 'none' ? 'block' : 'none';
}
function selectOtherIcon(ic) {
otherSelectedIcon = ic;
[Link]('other-icon-preview').textContent = ic;
[Link]('other-icon-picker').[Link] = 'none';
}
function otherFileChosen(event) {
const file = [Link][0];
if (!file) return;
otherFileOriginalName = [Link](/\.html?$/i, '');
const fnInput = [Link]('other-fname');
if (![Link]()) [Link] = otherFileOriginalName;
const reader = new FileReader();
[Link] = function(e) {
otherFileContent = [Link];
[Link]('other-file-info').textContent = `' File
loaded: ${[Link]} (${([Link]/1024).toFixed(1)} KB)`;
};
[Link](file);
}
async function uploadOtherFile() {
const name = [Link]('other-fname').[Link]();
if (!name) { showToast('& þ Pehle app ka naam daalo!'); return; }
if (!otherFileContent) { showToast('& þ Pehle HTML file choose karo!');
return; }
try {
const r = await fetch(`${FLASK_API}/modules/save`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: [Link]({ filename: name, content: otherFileContent })
});
const d = await [Link]();
if ([Link] === 'ok') {
// Save icon meta
const key = [Link](/\.html$/i, '');
await fetch(`${FLASK_API}/modules/meta`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: [Link]({ key, icon: otherSelectedIcon })
});
showToast(`' ${name} save ho gaya!`);
[Link]('other-fname').value = '';
[Link]('other-file-info').textContent = '';
[Link]('other-file-input').value = '';
[Link]('other-icon-preview').textContent = 'Ø>Ýé';
otherFileContent = '';
otherFileOriginalName = '';
otherSelectedIcon = 'Ø>Ýé';
loadOtherList();
} else {
showToast(''L Error: ' + ([Link] || 'unknown'));
}
} catch {
showToast(''L Flask server se connect nahi hua!');
}
}
async function loadOtherList() {
const listDiv = [Link]('other-files-list');
if (!listDiv) return;
try {
const [listRes, metaRes] = await [Link]([
fetch(`${FLASK_API}/modules/list`),
fetch(`${FLASK_API}/modules/meta`)
]);
const listData = await [Link]();
const metaData = await [Link]();
const meta = [Link] || {};
if (![Link] || [Link] === 0) {
[Link] = `<div
style="text-align:center;padding:30px;color:var(--text2);">
<div style="font-size:2rem;margin-bottom:8px;">Ø=Üí</div>
<div style="font-size:0.85rem;">Koi app nahi. Upar se add
karo!</div>
</div>`;
return;
}
// App icon grid
[Link] = `<div style="display:grid;grid-template-columns:re
peat(auto-fill,minmax(110px,1fr));gap:14px;padding:4px;">
${[Link](f => {
const key = [Link](/\.html$/i, '');
const m = meta[key] || {};
const icon = [Link] || 'Ø>Ýé';
const lastOpened = m.last_opened
? new Date(m.last_opened).toLocaleDateString('en-IN',{day:'num
eric',month:'short',hour:'2-digit',minute:'2-digit'})
: null;
return `<div
style="display:flex;flex-direction:column;align-items:center;gap:6px;
background:var(--surface2);border-radius:14px;padding:16px
8px 12px;
border:1.5px solid var(--border);cursor:pointer;
transition:transform 0.18s,border-color 0.18s,box-shadow 0.18s;
position:relative;"
onclick="openOtherFile('${[Link]}','${[Link]}')"
onmouseenter="[Link]='translateY(-3px)';[Link].b
orderColor='var(--accent)';[Link]='var(--shadow-lg)'"
onmouseleave="[Link]='translateY(0)';[Link]
erColor='var(--border)';[Link]='none'">
<!-- options button -->
<div style="position:absolute;top:6px;right:6px;"
onclick="[Link]()">
<button class="btn btn-ghost" style="padding:2px
6px;font-size:0.85rem;border-radius:6px;"
onclick="toggleAppMenu('${key}')" title="Options">"î</button>
<div id="appmenu-${key}"
style="display:none;position:absolute;right:0;top:24px;
background:var(--surface);border:1px solid
var(--border);border-radius:10px;
box-shadow:var(--shadow-lg);z-index:200;min-width:120px;ove
rflow:hidden;">
<button class="btn btn-ghost"
style="width:100%;text-align:left;padding:8px
14px;font-size:0.82rem;border-radius:0;"
onclick="startRenameApp('${key}','${[Link]}')">'þ
Rename</button>
<button class="btn btn-ghost"
style="width:100%;text-align:left;padding:8px
14px;font-size:0.82rem;border-radius:0;"
onclick="changeAppIcon('${key}')">Ø<ߨ Icon Badlo</button>
<button class="btn btn-ghost"
style="width:100%;text-align:left;padding:8px
14px;font-size:0.82rem;color:var(--danger);border-radius:0;"
onclick="deleteOtherFile('${[Link]}')">Ø=ÝÑ Delete</button>
</div>
</div>
<div
style="font-size:2.4rem;line-height:1;margin-bottom:2px;">${icon}</div>
<div
style="font-size:0.8rem;font-weight:600;color:var(--text);text-align:center;
word-break:break-word;line-height:1.3;max-width:90px;">${[Link]
me}</div>
${lastOpened ? `<div style="font-size:0.65rem;color:var(--text2);te
xt-align:center;line-height:1.2;">${lastOpened}</div>` : ''}
<div
style="font-size:0.65rem;color:var(--text2);">${([Link]/1024).toFixed(1)}
KB</div>
</div>`;
}).join('')}
</div>`;
// Close menus on outside click
[Link]('click', closeAllAppMenus, { once: true });
} catch {
[Link] = `<div style="text-align:center;padding:20px;color:
var(--text2);font-size:0.85rem;">& þ Flask server se list nahi mili</div>`;
}
}
function toggleAppMenu(key) {
const menu = [Link](`appmenu-${key}`);
if (!menu) return;
const wasHidden = [Link] === 'none';
closeAllAppMenus();
if (wasHidden) {
[Link] = 'block';
setTimeout(() => [Link]('click',
closeAllAppMenus, { once: true }), 10);
}
}
function closeAllAppMenus() {
[Link]('[id^="appmenu-"]').forEach(m =>
[Link] = 'none');
}
function openOtherFile(filename, name) {
const url =
`${FLASK_API}/modules/file/${encodeURIComponent(filename)}`;
[Link](url, '_blank');
showToast(`%¶ ${name} khul raha hai...`);
// Refresh list after open (to update last_opened)
setTimeout(loadOtherList, 1200);
}
function startRenameApp(key, currentName) {
closeAllAppMenus();
const newName = prompt(`"${currentName}" ka naya naam:`,
currentName);
if (!newName || [Link]() === currentName) return;
renameOtherFile(key, [Link]());
}
async function renameOtherFile(oldKey, newName) {
try {
const r = await fetch(`${FLASK_API}/modules/rename`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: [Link]({ old: oldKey, new: newName })
});
const d = await [Link]();
if ([Link] === 'ok') {
showToast(`' Rename ho gaya !’ ${newName}`);
loadOtherList();
} else {
showToast(''L ' + ([Link] || 'Rename fail'));
}
} catch {
showToast(''L Server se connect nahi hua!');
}
}
function changeAppIcon(key) {
closeAllAppMenus();
// Show a small inline icon picker in a toast-style prompt
const chosen = prompt(`Naya icon paste karo (koi bhi emoji):\nJaise:
Ø=ܰ Ø=ÜÊ ' Ø<߯ Ø<ßËþ Ø=ÜÚ`, 'Ø>Ýé');
if (!chosen) return;
const icon = [Link]().slice(0, 4); // emoji max
fetch(`${FLASK_API}/modules/meta`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: [Link]({ key, icon })
}).then(() => {
showToast(`Ø<ߨ Icon update ho gaya!`);
loadOtherList();
}).catch(() => showToast(''L Server error'));
}
function reloadOtherViewer() {}
function closeOtherViewer() {}
async function deleteOtherFile(filename) {
closeAllAppMenus();
if (!confirm(`"${filename}" delete karna chahte ho?`)) return;
try {
const r = await fetch(`${FLASK_API}/modules/delete`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: [Link]({ filename })
});
const d = await [Link]();
if ([Link] === 'ok') {
showToast(`Ø=ÝÑ ${filename} delete ho gaya!`);
loadOtherList();
} else {
showToast(''L ' + ([Link] || 'Delete fail'));
}
} catch {
showToast(''L Server se connect nahi hua!');
}
}
// =====================================
// RUN
// =====================================
init();
[Link]('load', () => {
updateFlaskStatus();
});
</script>
</body>
</html>