first mobile improvements

This commit is contained in:
2026-02-10 21:12:49 +01:00
parent fcfd2d5662
commit 6621c622d3
3 changed files with 177 additions and 25 deletions

View File

@@ -1,9 +1,45 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block content %} {% block content %}
<style>
/* Specific Dashboard Mobile Fixes */
@media (max-width: 768px) {
.activity-grid {
grid-template-columns: repeat(2, 1fr) !important; /* 2 columns on mobile */
}
.active-section {
/* Active timer card styles for mobile */
position: fixed;
bottom: 70px; /* Above nav bar */
left: 10px; right: 10px;
z-index: 900;
margin: 0 !important;
padding: 1rem !important;
border-radius: 12px;
box-shadow: 0 -5px 20px rgba(0,0,0,0.1);
border: 1px solid var(--primary-color);
}
/* Create space for the fixed active card so list isn't hidden */
body.is-tracking .container {
padding-bottom: 350px !important;
}
/* Mobile specific timer adjustments */
.timer-display { font-size: 2.5rem !important; margin: 0.5rem 0 1rem 0 !important; }
}
</style>
<script>
// Helper to toggle body class for spacing
function setTrackingState(isTracking) {
if(isTracking) document.body.classList.add('is-tracking');
else document.body.classList.remove('is-tracking');
}
</script>
<div style="display: flex; flex-wrap: wrap; gap: 2rem; align-items: flex-start;"> <div style="display: flex; flex-wrap: wrap; gap: 2rem; align-items: flex-start;">
<!-- Left Column: Activity Grid (Main Content) --> <!-- Left Column: Activity Grid (Main Content) -->
<div style="flex: 1; min-width: 300px;"> <div style="flex: 1; min-width: 300px; max-width: 100%;">
<h2 style="margin-bottom: 1.5rem;">Start Tracking</h2> <h2 style="margin-bottom: 1.5rem;">Start Tracking</h2>
<div class="activity-grid"> <div class="activity-grid">
@@ -66,14 +102,17 @@
<!-- Right Column: Sidebar (Active Timer) --> <!-- Right Column: Sidebar (Active Timer) -->
<!-- Only visible if tracking, or hidden via JS initially if not tracking but we use 'display:none' on the card itself --> <!-- Only visible if tracking, or hidden via JS initially if not tracking but we use 'display:none' on the card itself -->
<div style="flex: 0 0 380px; position: sticky; top: 100px; max-width: 100%;"> <div style="flex: 0 0 380px; position: sticky; top: 100px; max-width: 100%;" class="sidebar-container">
<div class="card active-section" id="activeSection" style="{% if not current_entry %}display:none;{% endif %} padding: 1.5rem;"> <div class="card active-section" id="activeSection" style="{% if not current_entry %}display:none;{% endif %} padding: 1.5rem;">
<div style="text-align: center; margin-bottom: 1rem;"> <div style="display:flex; justify-content: space-between; align-items: start;">
<small style="text-transform: uppercase; font-weight: bold; color: var(--primary-color);">Currently Tracking</small> <div style="text-align: center; flex: 1;">
<h3 id="activeName" style="margin: 0.5rem 0; color: {{ current_entry.activity_color if current_entry else '#333' }}"> <small style="text-transform: uppercase; font-weight: bold; color: var(--primary-color);">Currently Tracking</small>
{{ current_entry.activity_name if current_entry else '' }} <h3 id="activeName" style="margin: 0.5rem 0; color: {{ current_entry.activity_color if current_entry else '#333' }}">
</h3> {{ current_entry.activity_name if current_entry else '' }}
</h3>
</div>
<!-- Collapse Button for Mobile (Optional, keeping simple for now) -->
</div> </div>
<!-- Context Info --> <!-- Context Info -->
@@ -124,6 +163,9 @@
let startTime = {% if current_entry %}{{ current_entry.start_time.timestamp() * 1000 }}{% else %}null{% endif %}; let startTime = {% if current_entry %}{{ current_entry.start_time.timestamp() * 1000 }}{% else %}null{% endif %};
let timerInterval; let timerInterval;
// Initial state check for padding
if(startTime) setTrackingState(true);
function updateTimer() { function updateTimer() {
if (!startTime) return; if (!startTime) return;
const now = new Date().getTime(); const now = new Date().getTime();
@@ -194,6 +236,8 @@
// Update UI State // Update UI State
document.getElementById('activeSection').style.display = 'block'; document.getElementById('activeSection').style.display = 'block';
setTrackingState(true); // Add padding for mobile
document.getElementById('activeName').innerText = name; document.getElementById('activeName').innerText = name;
document.getElementById('activeName').style.color = color; document.getElementById('activeName').style.color = color;

View File

@@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>OpenTimeTracker</title> <title>OpenTimeTracker</title>
<!-- Favicon: Simple Clock SVG --> <!-- Favicon: Simple Clock SVG -->
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22%232383e2%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22><circle cx=%2212%22 cy=%2212%22 r=%2210%22/><polyline points=%2212 6 12 12 16 14%22/></svg>"> <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22%232383e2%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22><circle cx=%2212%22 cy=%2212%22 r=%2210%22/><polyline points=%2212 6 12 12 16 14%22/></svg>">
@@ -26,20 +26,22 @@
margin: 0; margin: 0;
padding: 0; padding: 0;
line-height: 1.6; line-height: 1.6;
-webkit-text-size-adjust: 100%;
} }
/* Modern Navigation */ /* Modern Navigation (Top) */
nav { .top-nav {
background: rgba(255, 255, 255, 0.95); background: rgba(255, 255, 255, 0.98); /* Less transparent for stability */
backdrop-filter: blur(10px);
padding: 0.8rem 1.5rem;
border-bottom: 1px solid var(--border-dim); border-bottom: 1px solid var(--border-dim);
padding: 0.8rem 1.5rem;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 1000; z-index: 100; /* Lower than modal */
height: 60px;
box-sizing: border-box;
} }
.brand { .brand {
@@ -50,7 +52,7 @@
display: flex; align-items: center; gap: 8px; display: flex; align-items: center; gap: 8px;
} }
nav a { .top-nav a {
color: var(--text-secondary); color: var(--text-secondary);
text-decoration: none; text-decoration: none;
margin-left: 20px; margin-left: 20px;
@@ -58,9 +60,16 @@
font-weight: 500; font-weight: 500;
transition: color 0.15s; transition: color 0.15s;
} }
nav a:hover { color: var(--text-primary); } .top-nav a:hover { color: var(--text-primary); }
.container { max-width: 1100px; margin: 3rem auto; padding: 0 1.5rem; } .container {
max-width: 1100px;
margin: 2rem auto;
padding: 0 1.5rem;
min-height: 80vh; /* Ensure content area has height */
position: relative;
z-index: 1;
}
/* Clean Notion-style Cards */ /* Clean Notion-style Cards */
.card { .card {
@@ -103,6 +112,7 @@
color: var(--text-primary); color: var(--text-primary);
font-family: inherit; font-family: inherit;
transition: all 0.2s; transition: all 0.2s;
-webkit-appearance: none; /* iOS fix */
} }
input:focus, select:focus, textarea:focus { input:focus, select:focus, textarea:focus {
@@ -115,7 +125,6 @@
/* Custom Select Style */ /* Custom Select Style */
select { select {
appearance: none; appearance: none;
-webkit-appearance: none;
background-image: url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%2337352f%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E"); background-image: url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%2337352f%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E");
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: right 12px center; background-position: right 12px center;
@@ -249,6 +258,7 @@
top: 0; left: 0; width: 100%; height: 100%; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0, 0, 0, 0.4); background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(3px); backdrop-filter: blur(3px);
-webkit-backdrop-filter: blur(3px);
z-index: 2000; z-index: 2000;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -292,28 +302,104 @@
::-webkit-calendar-picker-indicator:hover { ::-webkit-calendar-picker-indicator:hover {
opacity: 1; opacity: 1;
} }
/* Mobile Responsive Styles */
.bottom-nav { display: none; }
.mobile-only { display: none; }
@media (max-width: 768px) {
.desktop-links { display: none; }
.top-nav { padding: 0.8rem 1rem; }
.container {
margin: 1.5rem auto;
padding-bottom: 100px;
padding-left: 1rem;
padding-right: 1rem;
}
/* Bottom Navigation */
.bottom-nav {
display: flex;
position: fixed;
bottom: 0; left: 0; width: 100%;
background: white;
border-top: 1px solid var(--border-dim);
justify-content: space-around;
padding: 10px 0 25px 0; /* Extra padding for iOS home indicator */
z-index: 1000;
box-shadow: 0 -2px 10px rgba(0,0,0,0.02);
}
.nav-item {
display: flex;
flex-direction: column;
align-items: center;
text-decoration: none;
color: var(--text-secondary);
font-size: 0.7rem;
gap: 4px;
padding: 5px;
flex: 1;
}
.nav-item svg { stroke-width: 2px; }
.nav-item.active { color: var(--primary-color); }
.nav-item.active svg { stroke: var(--primary-color); }
/* Mobile Modals (Bottom Sheet) */
.modal-backdrop { align-items: flex-end; }
.modal-card {
width: 100%;
max-width: 100%;
border-radius: 16px 16px 0 0;
margin: 0;
max-height: 85vh;
animation: slideUp 0.3s ease-out;
}
@keyframes slideUp {
from { transform: translateY(100%); }
to { transform: translateY(0); }
}
/* Adjust header for mobile */
h2 { font-size: 1.5rem; }
.mobile-only { display: block; }
}
</style> </style>
</head> </head>
<body> <body>
<nav> <nav class="top-nav">
<a href="{{ url_for('index') }}" class="brand" style="text-decoration: none;"> <a href="{{ url_for('index') }}" class="brand" style="text-decoration: none;">
<!-- Simple SVG Icon --> <!-- Simple SVG Icon -->
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg> <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
OpenTimeTracker OpenTimeTracker
</a> </a>
<div>
<!-- Desktop Navigation -->
<div class="desktop-links">
{% if session.user_id %} {% if session.user_id %}
<a href="{{ url_for('index') }}">Tracker</a> <a href="{{ url_for('index') }}">Tracker</a>
<a href="{{ url_for('tasks') }}">Tasks</a> <a href="{{ url_for('tasks') }}">Tasks</a>
<a href="{{ url_for('goals') }}">Goals</a> <a href="{{ url_for('goals') }}">Goals</a>
<a href="{{ url_for('logbook') }}">Logbook</a> <a href="{{ url_for('logbook') }}">Logbook</a>
<a href="{{ url_for('logout') }}">Logout</a> <a href="{{ url_for('logout') }}" style="color: var(--danger-color);">Logout</a>
{% else %} {% else %}
<a href="{{ url_for('login') }}">Login</a> <a href="{{ url_for('login') }}">Login</a>
<a href="{{ url_for('register') }}">Register</a> <a href="{{ url_for('register') }}">Register</a>
{% endif %} {% endif %}
</div> </div>
<!-- Mobile: Small user icon or logout -->
{% if session.user_id %}
<div class="mobile-only">
<a href="{{ url_for('logout') }}" style="color: var(--text-secondary); text-decoration: none;">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
</a>
</div>
{% endif %}
</nav> </nav>
<div class="container"> <div class="container">
{% with messages = get_flashed_messages() %} {% with messages = get_flashed_messages() %}
{% if messages %} {% if messages %}
@@ -324,5 +410,27 @@
{% endwith %} {% endwith %}
{% block content %}{% endblock %} {% block content %}{% endblock %}
</div> </div>
<!-- Mobile Bottom Navigation (Only authenticated) -->
{% if session.user_id %}
<nav class="bottom-nav">
<a href="{{ url_for('index') }}" class="nav-item {% if request.endpoint == 'index' %}active{% endif %}">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
<span>Tracker</span>
</a>
<a href="{{ url_for('tasks') }}" class="nav-item {% if request.endpoint == 'tasks' or request.endpoint == 'task_detail' %}active{% endif %}">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
<span>Tasks</span>
</a>
<a href="{{ url_for('goals') }}" class="nav-item {% if request.endpoint == 'goals' %}active{% endif %}">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/></svg>
<span>Goals</span>
</a>
<a href="{{ url_for('logbook') }}" class="nav-item {% if request.endpoint == 'logbook' %}active{% endif %}">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>
<span>Logbook</span>
</a>
</nav>
{% endif %}
</body> </body>
</html> </html>

View File

@@ -15,14 +15,14 @@
</div> </div>
<!-- Filters --> <!-- Filters -->
<div style="border-bottom: 1px solid var(--border-dim); margin-bottom: 2rem; padding-bottom: 0.5rem; display: flex; gap: 20px;"> <div style="border-bottom: 1px solid var(--border-dim); margin-bottom: 2rem; padding-bottom: 0.5rem; display: flex; gap: 20px; overflow-x: auto; white-space: nowrap; -webkit-overflow-scrolling: touch;">
<div class="filter-tab active" id="filter-open" onclick="setFilter('open')" data-filter="open" style="cursor: pointer; font-weight: 600; font-size: 0.9rem; color: var(--text-primary);"> <div class="filter-tab active" id="filter-open" onclick="setFilter('open')" data-filter="open" style="cursor: pointer; font-weight: 600; font-size: 0.9rem; color: var(--text-primary); flex-shrink: 0;">
Open Open
</div> </div>
<div class="filter-tab" id="filter-today" onclick="setFilter('today')" data-filter="today" style="cursor: pointer; font-weight: 600; font-size: 0.9rem; color: var(--text-secondary);"> <div class="filter-tab" id="filter-today" onclick="setFilter('today')" data-filter="today" style="cursor: pointer; font-weight: 600; font-size: 0.9rem; color: var(--text-secondary); flex-shrink: 0;">
Due Today Due Today
</div> </div>
<div class="filter-tab" id="filter-completed" onclick="setFilter('completed')" data-filter="completed" style="cursor: pointer; font-weight: 600; font-size: 0.9rem; color: var(--text-secondary);"> <div class="filter-tab" id="filter-completed" onclick="setFilter('completed')" data-filter="completed" style="cursor: pointer; font-weight: 600; font-size: 0.9rem; color: var(--text-secondary); flex-shrink: 0;">
Completed Completed
</div> </div>
</div> </div>