feat: re-design
This commit is contained in:
+7
-1
@@ -5,12 +5,18 @@
|
|||||||
<link rel="icon" href="/favicon.ico">
|
<link rel="icon" href="/favicon.ico">
|
||||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
||||||
<meta name="theme-color" content="#0a0e1a">
|
<meta name="theme-color" content="#12161d">
|
||||||
<meta name="color-scheme" content="dark">
|
<meta name="color-scheme" content="dark">
|
||||||
<meta name="mobile-web-app-capable" content="yes">
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||||
<meta name="apple-mobile-web-app-title" content="dttmr">
|
<meta name="apple-mobile-web-app-title" content="dttmr">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=IBM+Plex+Sans:wght@400;500;600;700&family=IBM+Plex+Serif:wght@500;600;700&family=Special+Elite&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
>
|
||||||
<title>dttmr</title>
|
<title>dttmr</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
+47
-37
@@ -1,33 +1,40 @@
|
|||||||
/* dttmr color palette - dark, modern, blueish */
|
/* dttmr palette - field-ledger / stamped ink, cool slate ground, blue ink accent */
|
||||||
:root {
|
:root {
|
||||||
--c-bg: #0a0e1a;
|
--c-bg: #12161d;
|
||||||
--c-bg-soft: #101627;
|
--c-bg-soft: #181e27;
|
||||||
--c-bg-mute: #161d33;
|
--c-bg-mute: #202632;
|
||||||
--c-bg-elevated: #1a2338;
|
--c-bg-elevated: #262e3c;
|
||||||
|
|
||||||
--c-border: rgba(120, 150, 220, 0.16);
|
--c-border: rgba(74, 130, 196, 0.16);
|
||||||
--c-border-hover: rgba(120, 150, 220, 0.32);
|
--c-border-hover: rgba(74, 130, 196, 0.36);
|
||||||
|
|
||||||
--c-text: #c7d0e6;
|
--c-text: #aab2c0;
|
||||||
--c-text-soft: #8892b0;
|
--c-text-soft: #707a8c;
|
||||||
--c-heading: #f1f4fc;
|
--c-heading: #eef1f6;
|
||||||
|
|
||||||
--c-accent: #4f7dfa;
|
--c-accent: #3d72b4;
|
||||||
--c-accent-soft: #3a5fd9;
|
--c-accent-soft: #2f5c92;
|
||||||
--c-accent-strong: #6c93ff;
|
--c-accent-strong: #62a0e0;
|
||||||
--c-accent-bg: rgba(79, 125, 250, 0.14);
|
--c-accent-bg: rgba(61, 114, 180, 0.16);
|
||||||
|
|
||||||
--c-success: #34d399;
|
--c-success: #6ba178;
|
||||||
--c-danger: #f87171;
|
--c-success-bg: rgba(107, 161, 120, 0.14);
|
||||||
--c-danger-bg: rgba(248, 113, 113, 0.12);
|
--c-danger: #c1614a;
|
||||||
--c-warning: #fbbf24;
|
--c-danger-bg: rgba(193, 97, 74, 0.14);
|
||||||
|
--c-warning: #cf9d3f;
|
||||||
|
|
||||||
--radius-sm: 8px;
|
--font-body:
|
||||||
--radius-md: 12px;
|
'IBM Plex Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
--radius-lg: 18px;
|
--font-display: 'IBM Plex Serif', Georgia, 'Times New Roman', serif;
|
||||||
|
--font-mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||||
|
--font-stamp: 'Special Elite', var(--font-mono);
|
||||||
|
|
||||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
|
--radius-sm: 3px;
|
||||||
--shadow-md: 0 8px 24px rgba(0, 0, 0, 0.45);
|
--radius-md: 5px;
|
||||||
|
--radius-lg: 8px;
|
||||||
|
|
||||||
|
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.45);
|
||||||
|
--shadow-md: 0 10px 28px rgba(0, 0, 0, 0.5);
|
||||||
|
|
||||||
--nav-height: 60px;
|
--nav-height: 60px;
|
||||||
--safe-bottom: env(safe-area-inset-bottom, 0px);
|
--safe-bottom: env(safe-area-inset-bottom, 0px);
|
||||||
@@ -56,21 +63,18 @@ html {
|
|||||||
body {
|
body {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
color: var(--c-text);
|
color: var(--c-text);
|
||||||
background: radial-gradient(circle at top, #101a33 0%, var(--c-bg) 55%);
|
background-color: var(--c-bg);
|
||||||
|
background-image:
|
||||||
|
repeating-linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
rgba(74, 130, 196, 0.06) 0,
|
||||||
|
rgba(74, 130, 196, 0.06) 1px,
|
||||||
|
transparent 1px,
|
||||||
|
transparent 28px
|
||||||
|
),
|
||||||
|
radial-gradient(circle at 50% -10%, rgba(74, 130, 196, 0.08), transparent 55%);
|
||||||
line-height: 1.55;
|
line-height: 1.55;
|
||||||
font-family:
|
font-family: var(--font-body);
|
||||||
Inter,
|
|
||||||
-apple-system,
|
|
||||||
BlinkMacSystemFont,
|
|
||||||
'Segoe UI',
|
|
||||||
Roboto,
|
|
||||||
Oxygen,
|
|
||||||
Ubuntu,
|
|
||||||
Cantarell,
|
|
||||||
'Fira Sans',
|
|
||||||
'Droid Sans',
|
|
||||||
'Helvetica Neue',
|
|
||||||
sans-serif;
|
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
@@ -85,6 +89,12 @@ h4 {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: var(--c-accent-strong);
|
color: var(--c-accent-strong);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|||||||
+37
-5
@@ -29,13 +29,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
background: linear-gradient(135deg, var(--c-accent) 0%, var(--c-accent-soft) 100%);
|
background-color: var(--c-accent);
|
||||||
color: #ffffff;
|
color: var(--c-bg);
|
||||||
|
font-weight: 600;
|
||||||
box-shadow: var(--shadow-sm);
|
box-shadow: var(--shadow-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary:hover:not(:disabled) {
|
.btn-primary:hover:not(:disabled) {
|
||||||
background: linear-gradient(135deg, var(--c-accent-strong) 0%, var(--c-accent) 100%);
|
background-color: var(--c-accent-strong);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-secondary {
|
.btn-secondary {
|
||||||
@@ -52,7 +53,7 @@
|
|||||||
.btn-danger {
|
.btn-danger {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: var(--c-danger);
|
color: var(--c-danger);
|
||||||
border-color: rgba(248, 113, 113, 0.35);
|
border-color: rgba(193, 97, 74, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-danger:hover:not(:disabled) {
|
.btn-danger:hover:not(:disabled) {
|
||||||
@@ -113,10 +114,41 @@
|
|||||||
|
|
||||||
.banner-error {
|
.banner-error {
|
||||||
background-color: var(--c-danger-bg);
|
background-color: var(--c-danger-bg);
|
||||||
border: 1px solid rgba(248, 113, 113, 0.4);
|
border: 1px solid rgba(193, 97, 74, 0.4);
|
||||||
color: var(--c-danger);
|
color: var(--c-danger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.banner-success {
|
||||||
|
background-color: var(--c-success-bg);
|
||||||
|
border: 1px solid rgba(127, 174, 120, 0.4);
|
||||||
|
color: var(--c-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mono-num {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Rubber-stamp badge: the app's signature mark, used for sync status. */
|
||||||
|
.stamp {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.3rem;
|
||||||
|
padding: 0.15rem 0.55rem;
|
||||||
|
border: 1.5px solid currentColor;
|
||||||
|
border-radius: 999px;
|
||||||
|
box-shadow: 0 0 0 2px var(--c-bg) inset;
|
||||||
|
outline: 1px solid currentColor;
|
||||||
|
outline-offset: 2px;
|
||||||
|
transform: rotate(-3deg);
|
||||||
|
font-family: var(--font-stamp);
|
||||||
|
font-size: 0.62rem;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
line-height: 1.4;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.page {
|
.page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
padding: 1rem 1rem calc(var(--nav-height) + var(--safe-bottom) + 1.5rem);
|
padding: 1rem 1rem calc(var(--nav-height) + var(--safe-bottom) + 1.5rem);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import { useListsStore } from '@/stores/lists'
|
import { useListsStore } from '@/stores/lists'
|
||||||
@@ -11,25 +12,49 @@ async function handleLogout() {
|
|||||||
await authStore.logout()
|
await authStore.logout()
|
||||||
router.push('/login')
|
router.push('/login')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const stampLabel = computed(() => {
|
||||||
|
if (!authStore.isAuthenticated) return 'offline'
|
||||||
|
if (listsStore.isSyncing) return 'syncing'
|
||||||
|
if (listsStore.pendingCount > 0) return `${listsStore.pendingCount} pending`
|
||||||
|
return 'synced'
|
||||||
|
})
|
||||||
|
|
||||||
|
const stampClass = computed(() => {
|
||||||
|
if (!authStore.isAuthenticated) return ''
|
||||||
|
if (listsStore.pendingCount > 0 || listsStore.isSyncing) return 'stamp-pending'
|
||||||
|
return 'stamp-synced'
|
||||||
|
})
|
||||||
|
|
||||||
|
const stampTitle = computed(() => {
|
||||||
|
if (!authStore.isAuthenticated) return 'Not signed in'
|
||||||
|
if (listsStore.isSyncing) return 'Syncing…'
|
||||||
|
if (listsStore.pendingCount > 0) return `${listsStore.pendingCount} change(s) waiting to sync`
|
||||||
|
return 'Up to date'
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<header class="app-header">
|
<header class="app-header">
|
||||||
<div class="brand">
|
<div class="brand">
|
||||||
<span class="brand-dot"></span>
|
<span class="brand-seal"></span>
|
||||||
<span class="brand-name">dttmr</span>
|
<span class="brand-name">dttmr</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="status">
|
<div class="status">
|
||||||
<span
|
<span
|
||||||
class="sync-dot"
|
v-if="listsStore.error"
|
||||||
:class="{ offline: !authStore.isAuthenticated }"
|
class="stamp stamp-danger"
|
||||||
:title="listsStore.isSyncing ? 'Syncing…' : 'Up to date'"
|
:title="listsStore.error"
|
||||||
></span>
|
>sync error</span
|
||||||
<span v-if="listsStore.pendingCount > 0" class="pending">
|
>
|
||||||
{{ listsStore.pendingCount }} pending
|
<span
|
||||||
</span>
|
v-else
|
||||||
<span v-if="listsStore.error" class="sync-error" :title="listsStore.error">⚠ sync error</span>
|
class="stamp"
|
||||||
|
:class="stampClass"
|
||||||
|
:title="stampTitle"
|
||||||
|
>{{ stampLabel }}</span
|
||||||
|
>
|
||||||
<button v-if="authStore.isAuthenticated" type="button" class="logout" @click="handleLogout">
|
<button v-if="authStore.isAuthenticated" type="button" class="logout" @click="handleLogout">
|
||||||
Log out
|
Log out
|
||||||
</button>
|
</button>
|
||||||
@@ -46,7 +71,7 @@ async function handleLogout() {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: calc(0.75rem + var(--safe-top)) 1rem 0.75rem;
|
padding: calc(0.75rem + var(--safe-top)) 1rem 0.75rem;
|
||||||
background: linear-gradient(180deg, rgba(10, 14, 26, 0.92) 60%, rgba(10, 14, 26, 0));
|
background: linear-gradient(180deg, rgba(20, 24, 26, 0.92) 60%, rgba(20, 24, 26, 0));
|
||||||
backdrop-filter: blur(8px);
|
backdrop-filter: blur(8px);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,17 +79,22 @@ async function handleLogout() {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
font-weight: 600;
|
|
||||||
color: var(--c-heading);
|
color: var(--c-heading);
|
||||||
letter-spacing: 0.02em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.brand-dot {
|
.brand-name {
|
||||||
width: 10px;
|
font-family: var(--font-stamp);
|
||||||
height: 10px;
|
font-size: 0.95rem;
|
||||||
border-radius: 50%;
|
letter-spacing: 0.04em;
|
||||||
background: linear-gradient(135deg, var(--c-accent-strong), var(--c-accent-soft));
|
}
|
||||||
box-shadow: 0 0 12px var(--c-accent);
|
|
||||||
|
.brand-seal {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: var(--c-accent);
|
||||||
|
transform: rotate(-8deg);
|
||||||
|
box-shadow: 0 0 0 2px var(--c-bg) inset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status {
|
.status {
|
||||||
@@ -75,18 +105,19 @@ async function handleLogout() {
|
|||||||
color: var(--c-text-soft);
|
color: var(--c-text-soft);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sync-dot {
|
.stamp {
|
||||||
width: 8px;
|
color: var(--c-text-soft);
|
||||||
height: 8px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: var(--c-success);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sync-dot.offline {
|
.stamp-synced {
|
||||||
background-color: var(--c-text-soft);
|
color: var(--c-success);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sync-error {
|
.stamp-pending {
|
||||||
|
color: var(--c-accent-strong);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stamp-danger {
|
||||||
color: var(--c-danger);
|
color: var(--c-danger);
|
||||||
cursor: help;
|
cursor: help;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,14 +10,23 @@ const listsStore = useListsStore()
|
|||||||
<template>
|
<template>
|
||||||
<nav v-if="authStore.isAuthenticated" class="bottom-nav">
|
<nav v-if="authStore.isAuthenticated" class="bottom-nav">
|
||||||
<RouterLink to="/" class="nav-item" active-class="is-active">
|
<RouterLink to="/" class="nav-item" active-class="is-active">
|
||||||
<span class="nav-icon">☰</span>
|
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M4 6h1.5M4 12h1.5M4 18h1.5" stroke-linecap="round" />
|
||||||
|
<path d="M9 6h11M9 12h11M9 18h11" stroke-linecap="round" />
|
||||||
|
</svg>
|
||||||
<span class="nav-label">Lists</span>
|
<span class="nav-label">Lists</span>
|
||||||
<span v-if="listsStore.pendingCount > 0" class="nav-badge">{{
|
<span v-if="listsStore.pendingCount > 0" class="nav-badge mono-num">{{
|
||||||
listsStore.pendingCount
|
listsStore.pendingCount
|
||||||
}}</span>
|
}}</span>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<RouterLink to="/account" class="nav-item" active-class="is-active">
|
<RouterLink to="/account" class="nav-item" active-class="is-active">
|
||||||
<span class="nav-icon">⚙</span>
|
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<circle cx="12" cy="12" r="3.2" />
|
||||||
|
<path
|
||||||
|
d="M12 3.5v2M12 18.5v2M20.5 12h-2M5.5 12h-2M17.7 6.3l-1.4 1.4M7.7 16.3l-1.4 1.4M17.7 17.7l-1.4-1.4M7.7 7.7 6.3 6.3"
|
||||||
|
stroke-linecap="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
<span class="nav-label">Account</span>
|
<span class="nav-label">Account</span>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -45,14 +54,19 @@ const listsStore = useListsStore()
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 0.15rem;
|
gap: 0.2rem;
|
||||||
color: var(--c-text-soft);
|
color: var(--c-text-soft);
|
||||||
font-size: 0.7rem;
|
font-size: 0.68rem;
|
||||||
|
transition: color 0.15s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item.is-active {
|
||||||
|
color: var(--c-accent-strong);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-icon {
|
.nav-icon {
|
||||||
font-size: 1.25rem;
|
width: 21px;
|
||||||
line-height: 1;
|
height: 21px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-badge {
|
.nav-badge {
|
||||||
@@ -64,8 +78,9 @@ const listsStore = useListsStore()
|
|||||||
padding: 0 4px;
|
padding: 0 4px;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
background-color: var(--c-accent);
|
background-color: var(--c-accent);
|
||||||
color: #fff;
|
color: var(--c-bg);
|
||||||
font-size: 0.6rem;
|
font-size: 0.6rem;
|
||||||
|
font-weight: 600;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@@ -126,9 +126,6 @@ async function handleSubmit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.banner-success {
|
.banner-success {
|
||||||
background-color: rgba(52, 211, 153, 0.12);
|
|
||||||
border: 1px solid rgba(52, 211, 153, 0.4);
|
|
||||||
color: var(--c-success);
|
|
||||||
margin-top: 0.75rem;
|
margin-top: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -371,7 +371,7 @@ function inviteDetail(invite: Invite): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.invite-code {
|
.invite-code {
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
font-family: var(--font-mono);
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
letter-spacing: 0.06em;
|
letter-spacing: 0.06em;
|
||||||
color: var(--c-heading);
|
color: var(--c-heading);
|
||||||
@@ -388,7 +388,7 @@ function inviteDetail(invite: Invite): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.pill-active {
|
.pill-active {
|
||||||
background-color: rgba(52, 211, 153, 0.14);
|
background-color: var(--c-success-bg);
|
||||||
color: var(--c-success);
|
color: var(--c-success);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,7 +450,7 @@ function inviteDetail(invite: Invite): string {
|
|||||||
|
|
||||||
.ticket-btn-danger {
|
.ticket-btn-danger {
|
||||||
color: var(--c-danger);
|
color: var(--c-danger);
|
||||||
border-color: rgba(248, 113, 113, 0.35);
|
border-color: rgba(193, 97, 74, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ticket-btn-danger:hover:not(:disabled) {
|
.ticket-btn-danger:hover:not(:disabled) {
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ function handleDelete(event: Event) {
|
|||||||
<div class="list-card-main">
|
<div class="list-card-main">
|
||||||
<h3>{{ list.name }}</h3>
|
<h3>{{ list.name }}</h3>
|
||||||
<p class="meta">
|
<p class="meta">
|
||||||
{{ completedCount }}/{{ totalCount }} done
|
<span class="mono-num">{{ completedCount }}/{{ totalCount }}</span> done
|
||||||
<span v-if="list.pendingSync" class="pending-tag">syncing…</span>
|
<span v-if="list.pendingSync" class="pending-tag">syncing…</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -134,22 +134,23 @@ function handleDelete(event: Event) {
|
|||||||
|
|
||||||
.checkbox {
|
.checkbox {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 22px;
|
width: 21px;
|
||||||
height: 22px;
|
height: 21px;
|
||||||
border-radius: 6px;
|
border-radius: var(--radius-sm);
|
||||||
border: 1px solid var(--c-border-hover);
|
border: 1.5px solid var(--c-border-hover);
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: #fff;
|
color: var(--c-bg);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 0.75rem;
|
font-size: 0.8rem;
|
||||||
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-row.completed .checkbox {
|
.item-row.completed .checkbox {
|
||||||
background: linear-gradient(135deg, var(--c-accent-strong), var(--c-accent-soft));
|
background: var(--c-accent);
|
||||||
border-color: transparent;
|
border-color: var(--c-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
|
|||||||
@@ -110,9 +110,6 @@ async function handleAddUser() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.banner-success {
|
.banner-success {
|
||||||
background-color: rgba(52, 211, 153, 0.12);
|
|
||||||
border: 1px solid rgba(52, 211, 153, 0.4);
|
|
||||||
color: var(--c-success);
|
|
||||||
margin-top: 0.75rem;
|
margin-top: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ onMounted(() => {
|
|||||||
<h4>Sync status</h4>
|
<h4>Sync status</h4>
|
||||||
<p class="row">
|
<p class="row">
|
||||||
<span>Pending changes</span>
|
<span>Pending changes</span>
|
||||||
<strong>{{ listsStore.pendingCount }}</strong>
|
<strong class="mono-num">{{ listsStore.pendingCount }}</strong>
|
||||||
</p>
|
</p>
|
||||||
<p class="row">
|
<p class="row">
|
||||||
<span>Syncing</span>
|
<span>Syncing</span>
|
||||||
|
|||||||
+19
-8
@@ -46,7 +46,8 @@ async function handleSubmit() {
|
|||||||
<div class="login-container">
|
<div class="login-container">
|
||||||
<div class="login-card card">
|
<div class="login-card card">
|
||||||
<div class="brand-mark">
|
<div class="brand-mark">
|
||||||
<span class="brand-dot"></span>
|
<span class="brand-seal"></span>
|
||||||
|
<span class="brand-word">dttmr</span>
|
||||||
</div>
|
</div>
|
||||||
<h2>Login</h2>
|
<h2>Login</h2>
|
||||||
<p class="subtitle">Enter your credentials to access your account</p>
|
<p class="subtitle">Enter your credentials to access your account</p>
|
||||||
@@ -119,16 +120,26 @@ async function handleSubmit() {
|
|||||||
|
|
||||||
.brand-mark {
|
.brand-mark {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-bottom: 1rem;
|
gap: 0.55rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.brand-dot {
|
.brand-seal {
|
||||||
width: 40px;
|
width: 30px;
|
||||||
height: 40px;
|
height: 30px;
|
||||||
border-radius: 12px;
|
border-radius: 6px;
|
||||||
background: linear-gradient(135deg, var(--c-accent-strong), var(--c-accent-soft));
|
background: var(--c-accent);
|
||||||
box-shadow: 0 0 24px var(--c-accent-bg);
|
transform: rotate(-8deg);
|
||||||
|
box-shadow: 0 0 0 3px var(--c-bg-soft) inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-word {
|
||||||
|
font-family: var(--font-stamp);
|
||||||
|
font-size: 1.3rem;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
color: var(--c-heading);
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
|
|||||||
@@ -58,7 +58,8 @@ async function handleSubmit() {
|
|||||||
<div class="register-container">
|
<div class="register-container">
|
||||||
<div class="register-card card">
|
<div class="register-card card">
|
||||||
<div class="brand-mark">
|
<div class="brand-mark">
|
||||||
<span class="brand-dot"></span>
|
<span class="brand-seal"></span>
|
||||||
|
<span class="brand-word">dttmr</span>
|
||||||
</div>
|
</div>
|
||||||
<h2>Create your account</h2>
|
<h2>Create your account</h2>
|
||||||
|
|
||||||
@@ -160,16 +161,26 @@ async function handleSubmit() {
|
|||||||
|
|
||||||
.brand-mark {
|
.brand-mark {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-bottom: 1rem;
|
gap: 0.55rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.brand-dot {
|
.brand-seal {
|
||||||
width: 40px;
|
width: 30px;
|
||||||
height: 40px;
|
height: 30px;
|
||||||
border-radius: 12px;
|
border-radius: 6px;
|
||||||
background: linear-gradient(135deg, var(--c-accent-strong), var(--c-accent-soft));
|
background: var(--c-accent);
|
||||||
box-shadow: 0 0 24px var(--c-accent-bg);
|
transform: rotate(-8deg);
|
||||||
|
box-shadow: 0 0 0 3px var(--c-bg-soft) inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-word {
|
||||||
|
font-family: var(--font-stamp);
|
||||||
|
font-size: 1.3rem;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
color: var(--c-heading);
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
|
|||||||
+2
-2
@@ -23,8 +23,8 @@ export default defineConfig({
|
|||||||
description: 'dittmar.dev frontend app',
|
description: 'dittmar.dev frontend app',
|
||||||
start_url: '/',
|
start_url: '/',
|
||||||
scope: '/',
|
scope: '/',
|
||||||
theme_color: '#0a0e1a',
|
theme_color: '#12161d',
|
||||||
background_color: '#0a0e1a',
|
background_color: '#12161d',
|
||||||
display: 'standalone',
|
display: 'standalone',
|
||||||
icons: [
|
icons: [
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user