Files
PassKeeper/app/static/css/app.css
T
2026-04-22 22:10:46 -04:00

1218 lines
22 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ── Reset & base ──────────────────────────────────────────────────── */
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
:root {
--color-bg: #f4f5f7;
--color-surface: #ffffff;
--color-primary: #d0021b;
--color-primary-dark: #a80016;
--color-text: #1a1a2e;
--color-text-muted: #6b7280;
--color-border: #e2e4e9;
--color-sidebar-bg: #1e2235;
--color-sidebar-text: #c8ccd8;
--color-sidebar-hover: #2d3250;
--color-sidebar-active: #d0021b;
--radius: 8px;
--shadow: 0 1px 4px rgba(0, 0, 0, 0.08), 0 2px 12px rgba(0, 0, 0, 0.05);
--transition: 150ms ease;
}
html,
body {
height: 100%;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
font-size: 14px;
color: var(--color-text);
background: var(--color-bg);
}
a {
color: var(--color-primary);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
button {
cursor: pointer;
font: inherit;
border: none;
background: none;
}
input,
select,
textarea {
font: inherit;
}
ul {
list-style: none;
}
/* ── Utilities ───────────────────────────────────────────────────── */
.hidden {
display: none !important;
}
/* ── Auth pages ──────────────────────────────────────────────────── */
.auth-page {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
background: var(--color-bg);
}
.auth-container {
width: 100%;
max-width: 420px;
padding: 16px;
}
.auth-card {
background: var(--color-surface);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 40px 36px;
}
@media (max-width: 480px) {
.auth-container {
padding: 12px;
}
.auth-card {
padding: 28px 20px;
border-radius: 12px;
}
.auth-title {
font-size: 20px;
}
}
.auth-logo {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 28px;
}
.auth-logo .logo-icon {
font-size: 28px;
}
.auth-logo .logo-text {
font-size: 20px;
font-weight: 700;
color: var(--color-text);
}
.auth-title {
font-size: 22px;
font-weight: 600;
margin-bottom: 24px;
}
.auth-footer {
margin-top: 20px;
text-align: center;
color: var(--color-text-muted);
}
/* ── Form elements ──────────────────────────────────────────────── */
.form-group {
margin-bottom: 16px;
}
.form-group label {
display: block;
margin-bottom: 6px;
font-weight: 500;
color: var(--color-text);
}
.form-group label .hint {
font-weight: 400;
color: var(--color-text-muted);
font-size: 12px;
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 9px 12px;
border: 1px solid var(--color-border);
border-radius: var(--radius);
background: var(--color-surface);
color: var(--color-text);
transition: border-color var(--transition);
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: var(--color-primary);
box-shadow: 0 0 0 3px rgba(208, 2, 27, 0.12);
}
/* Prevent iOS viewport auto-zoom on input focus (requires font-size >= 16px) */
@media (max-width: 768px) {
.form-group input,
.form-group select,
.form-group textarea {
font-size: 16px;
}
}
.input-with-toggle {
position: relative;
}
.input-with-toggle input {
padding-right: 40px;
}
.btn-show-pass {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
cursor: pointer;
font-size: 16px;
opacity: 0.6;
transition: opacity var(--transition);
}
.btn-show-pass:hover {
opacity: 1;
}
.form-error {
color: #c00;
background: #fff0f0;
border: 1px solid #fcc;
border-radius: var(--radius);
padding: 8px 12px;
margin-bottom: 12px;
font-size: 13px;
}
/* Buttons */
.btn-primary {
display: inline-flex;
align-items: center;
justify-content: center;
background: var(--color-primary);
color: #fff;
padding: 10px 20px;
border-radius: var(--radius);
font-weight: 600;
transition: background var(--transition);
}
.btn-primary:hover {
background: var(--color-primary-dark);
}
.btn-primary:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn-full {
width: 100%;
}
.btn-secondary {
display: inline-flex;
align-items: center;
justify-content: center;
background: var(--color-border);
color: var(--color-text);
padding: 10px 20px;
border-radius: var(--radius);
font-weight: 500;
transition: background var(--transition);
}
.btn-secondary:hover {
background: #d1d5dc;
}
.btn-text {
background: none;
border: none;
color: inherit;
padding: 0;
}
/* Notices */
.notice-success {
background: #e8f5e9;
border: 1px solid #a5d6a7;
color: #2e7d32;
border-radius: var(--radius);
padding: 10px 14px;
margin-bottom: 16px;
font-size: 13px;
}
.notice-info {
background: #e3f2fd;
border: 1px solid #90caf9;
color: #1565c0;
border-radius: var(--radius);
padding: 10px 14px;
margin-bottom: 16px;
font-size: 13px;
}
/* ── Password Generator modal ────────────────────────────────────────── */
.gen-opt {
display: flex;
align-items: center;
gap: 6px;
cursor: pointer;
font-size: 13px;
}
.gen-opt input[type="checkbox"] {
width: 15px;
height: 15px;
accent-color: var(--color-primary);
cursor: pointer;
}
#gen-output {
letter-spacing: 0.5px;
font-family: "Courier New", monospace;
}
/* Launch (open URL) button — distinct colour so it reads as navigation */
.btn-icon[data-action="launch"] {
color: #1a73e8;
}
.btn-icon[data-action="launch"]:hover {
background: #e8f0fe;
color: #1558b0;
}
/* ── Vault list items ────────────────────────────────────────────── */
.vault-item {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 14px;
border-bottom: 1px solid var(--color-border);
cursor: default;
transition: background var(--transition);
}
.vault-item:last-child {
border-bottom: none;
}
.vault-item:hover {
background: #f9fafb;
}
.item-icon {
font-size: 20px;
flex-shrink: 0;
}
.item-info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 2px;
}
.item-name {
font-size: 14px;
font-weight: 600;
color: var(--color-text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.item-sub {
font-size: 12px;
color: var(--color-text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.item-actions {
display: flex;
align-items: center;
gap: 2px;
flex-shrink: 0;
}
/* ── TOTP live display in vault item ─────────────────────────────── */
.item-totp {
display: inline-flex;
align-items: center;
gap: 0;
font-size: 12px;
font-weight: 700;
font-family: "Courier New", Courier, monospace;
color: #1a73e8;
margin-top: 1px;
letter-spacing: 0.5px;
}
.totp-timer {
font-size: 11px;
font-weight: 400;
color: #9ca3af;
font-family: inherit;
}
/* Copy TOTP button — distinct teal so it reads as 2FA, not password */
.btn-icon[data-action="copy-totp"] {
color: #0891b2;
}
.btn-icon[data-action="copy-totp"]:hover {
background: #e0f2fe;
color: #0369a1;
}
/* Password strength */
.password-strength {
margin-top: 6px;
font-size: 12px;
font-weight: 500;
}
.strength-1 {
color: #c62828;
}
.strength-2 {
color: #e65100;
}
.strength-3 {
color: #f57f17;
}
.strength-4 {
color: #2e7d32;
}
.strength-5 {
color: #1b5e20;
}
/* ── App layout (vault) ──────────────────────────────────────────── */
.vault-page {
height: 100vh;
height: -webkit-fill-available;
height: calc(var(--vh, 1vh) * 100);
overflow: hidden;
}
.app-layout {
display: flex;
height: 100%;
}
/* ── Sidebar ─────────────────────────────────────────────────────── */
/* ── Base sidebar styles (shared by mobile and desktop) ─────────── */
.sidebar {
background: var(--color-sidebar-bg);
color: var(--color-sidebar-text);
display: flex;
flex-direction: column;
}
.sidebar-header {
display: flex;
align-items: center;
gap: 8px;
padding: 14px 12px;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
flex-shrink: 0;
min-height: 56px;
}
.sidebar-header .logo-icon {
font-size: 22px;
flex-shrink: 0;
}
.sidebar-header .logo-text {
font-size: 16px;
font-weight: 700;
color: #fff;
flex: 1;
white-space: nowrap;
overflow: hidden;
}
.sidebar-nav {
flex: 1;
padding: 8px 0;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.sidebar-section-header {
padding: 12px 16px 4px;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.08em;
color: rgba(255, 255, 255, 0.35);
white-space: nowrap;
overflow: hidden;
}
.sidebar-item {
display: flex;
align-items: center;
gap: 10px;
padding: 9px 16px;
color: var(--color-sidebar-text);
cursor: pointer;
transition: background var(--transition);
font-size: 14px;
width: 100%;
text-align: left;
position: relative;
white-space: nowrap;
overflow: hidden;
}
.sidebar-item:hover {
background: var(--color-sidebar-hover);
}
.sidebar-item.active {
background: var(--color-sidebar-active);
color: #fff;
}
.sidebar-icon {
font-size: 20px;
flex-shrink: 0;
width: 22px;
text-align: center;
}
.sidebar-label {
white-space: nowrap;
overflow: hidden;
}
.sidebar-footer {
padding: 12px 0;
border-top: 1px solid rgba(255, 255, 255, 0.08);
}
/* ── DESKTOP sidebar (body.is-desktop) ───────────────────────────── */
body.is-desktop .sidebar {
width: 240px;
flex-shrink: 0;
overflow: hidden;
/* Transition only on desktop so display:none on mobile is instant */
transition: width 0.22s ease;
}
body.is-desktop .sidebar.collapsed {
width: 72px;
}
body.is-desktop .sidebar.collapsed .sidebar-nav,
body.is-desktop .sidebar.collapsed .sidebar-footer {
overflow: visible;
}
body.is-desktop .sidebar.collapsed .sidebar-header {
justify-content: center;
padding: 14px 0;
overflow: visible;
}
body.is-desktop .sidebar-header .logo-icon {
transition: opacity 0.15s ease;
}
body.is-desktop .sidebar-header .logo-text {
flex: 1;
transition: opacity 0.15s ease;
}
body.is-desktop .sidebar.collapsed .sidebar-header .logo-icon,
body.is-desktop .sidebar.collapsed .sidebar-header .logo-text {
opacity: 0;
width: 0;
pointer-events: none;
}
body.is-desktop .btn-sidebar-toggle {
background: none;
border: none;
color: rgba(255, 255, 255, 0.5);
font-size: 14px;
cursor: pointer;
padding: 6px 8px;
border-radius: 4px;
line-height: 1;
flex-shrink: 0;
transition:
color var(--transition),
transform 0.22s ease;
margin-left: auto;
}
body.is-desktop .btn-sidebar-toggle:hover {
color: #fff;
background: rgba(255, 255, 255, 0.12);
}
body.is-desktop .sidebar.collapsed .btn-sidebar-toggle {
transform: rotate(180deg);
margin-left: 0;
}
body.is-desktop .sidebar-section-header {
transition: opacity 0.15s ease;
}
body.is-desktop .sidebar.collapsed .sidebar-section-header {
opacity: 0;
pointer-events: none;
height: 0;
padding: 0;
}
body.is-desktop .sidebar-label {
transition:
opacity 0.15s ease,
max-width 0.22s ease;
max-width: 200px;
}
body.is-desktop .sidebar.collapsed .sidebar-label {
opacity: 0;
max-width: 0;
}
body.is-desktop .sidebar.collapsed .sidebar-item {
justify-content: center;
padding: 10px 0;
overflow: visible;
}
body.is-desktop .sidebar.collapsed .sidebar-item::after {
content: attr(data-tooltip);
position: fixed;
left: 80px;
background: #111827;
color: #fff;
font-size: 12px;
padding: 5px 10px;
border-radius: 5px;
white-space: nowrap;
pointer-events: none;
opacity: 0;
transition: opacity 0.12s ease;
z-index: 1000;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
}
body.is-desktop .sidebar.collapsed .sidebar-item:hover::after {
opacity: 1;
}
/* ── MOBILE sidebar (body.is-mobile) ─────────────────────────────── */
body.is-mobile .sidebar {
/* Hidden by default — no layout space, no transitions to fight */
display: none;
position: fixed;
top: 0;
left: 0;
width: 280px;
height: 100%;
z-index: 90;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
/* No width/opacity transitions on mobile */
}
body.is-mobile .sidebar.mobile-open {
display: flex;
flex-direction: column;
}
/* Mobile sidebar always shows full labels regardless of collapsed state */
body.is-mobile .sidebar .sidebar-label {
opacity: 1;
max-width: none;
}
body.is-mobile .sidebar .sidebar-section-header {
opacity: 1;
height: auto;
padding: 12px 16px 4px;
pointer-events: auto;
}
body.is-mobile .sidebar .sidebar-item {
justify-content: flex-start;
padding: 9px 16px;
}
body.is-mobile .sidebar .sidebar-header {
justify-content: flex-start;
padding: 14px 12px;
}
body.is-mobile .sidebar .sidebar-header .logo-icon,
body.is-mobile .sidebar .sidebar-header .logo-text {
opacity: 1;
width: auto;
pointer-events: auto;
}
body.is-mobile .sidebar .sidebar-item::after {
display: none;
}
body.is-mobile .btn-sidebar-toggle {
display: none;
}
/* ── Mobile top bar — only on body.is-mobile ─────────────────────── */
.mobile-topbar {
display: none;
align-items: center;
gap: 12px;
padding: 0 16px;
height: 54px;
background: var(--color-sidebar-bg);
color: #fff;
flex-shrink: 0;
position: sticky;
top: 0;
z-index: 50;
}
body.is-mobile .mobile-topbar {
display: flex;
}
.btn-mobile-menu {
font-size: 22px;
color: rgba(255, 255, 255, 0.8);
background: none;
border: none;
padding: 4px 6px;
cursor: pointer;
flex-shrink: 0;
}
.btn-mobile-menu:hover {
color: #fff;
}
.mobile-logo {
flex: 1;
font-size: 16px;
font-weight: 700;
color: #fff;
}
.btn-fab-mobile {
width: 36px;
height: 36px;
font-size: 20px;
flex-shrink: 0;
}
/* Sidebar backdrop */
.sidebar-backdrop {
display: none;
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 89;
}
.sidebar-backdrop.open {
display: block;
}
/* ── App layout ──────────────────────────────────────────────────── */
body.is-desktop .app-layout {
display: flex;
height: 100vh;
height: calc(var(--vh, 1vh) * 100);
}
body.is-mobile .app-layout {
display: flex;
flex-direction: column;
height: 100vh;
height: -webkit-fill-available;
height: calc(var(--vh, 1vh) * 100);
overflow: hidden;
}
/* ── Folder items ────────────────────────────────────────────────── */
.folder-item {
justify-content: space-between;
}
.folder-label {
display: flex;
align-items: center;
gap: 10px;
flex: 1;
cursor: pointer;
}
.btn-delete-folder {
background: none;
border: none;
cursor: pointer;
font-size: 13px;
opacity: 0;
color: rgba(255, 255, 255, 0.6);
transition:
opacity var(--transition),
color var(--transition);
padding: 2px 4px;
border-radius: 4px;
flex-shrink: 0;
}
.folder-item:hover .btn-delete-folder {
opacity: 1;
}
.btn-delete-folder:hover {
color: #ff6b6b;
}
.btn-new-folder {
float: right;
margin-right: 4px;
background: none;
border: none;
color: rgba(255, 255, 255, 0.5);
font-size: 16px;
line-height: 1;
cursor: pointer;
padding: 0 4px;
transition: color var(--transition);
}
.btn-new-folder:hover {
color: #fff;
}
.new-folder-row {
padding: 4px 10px 4px 16px;
}
.new-folder-row form {
display: flex;
align-items: center;
gap: 4px;
}
.new-folder-row input {
flex: 1;
padding: 5px 8px;
border-radius: 4px;
border: 1px solid rgba(255, 255, 255, 0.2);
background: rgba(255, 255, 255, 0.1);
color: #fff;
font-size: 13px;
}
.new-folder-row input::placeholder {
color: rgba(255, 255, 255, 0.4);
}
.new-folder-row input:focus {
outline: none;
border-color: rgba(255, 255, 255, 0.5);
}
.new-folder-row .btn-icon {
color: rgba(255, 255, 255, 0.7);
font-size: 14px;
}
.new-folder-row .btn-icon:hover {
color: #fff;
background: rgba(255, 255, 255, 0.1);
}
/* ── Breakpoint-driven padding adjustments ───────────────────────── */
/* Tablet (7691024px) */
@media (min-width: 769px) and (max-width: 1024px) {
body.is-desktop .sidebar:not(.collapsed) {
width: 200px;
}
.vault-header {
padding: 14px 18px;
}
.vault-list {
padding: 10px 18px 18px;
}
.panel-body {
padding: 16px 18px;
}
.security-dashboard {
padding: 16px;
gap: 16px;
}
.search-wrapper {
max-width: 280px;
}
.security-summary {
flex-wrap: wrap;
gap: 12px;
}
.sec-stats {
flex-wrap: wrap;
gap: 10px;
}
.modal {
max-width: 460px;
}
}
/* Large PC (> 1440px) */
@media (min-width: 1441px) {
body.is-desktop .sidebar:not(.collapsed) {
width: 260px;
}
.vault-header {
padding: 18px 40px;
}
.vault-list {
padding: 16px 40px 40px;
}
.vault-list-inner {
max-width: 1100px;
}
.vault-header-inner {
max-width: 1100px;
}
.panel-body {
padding: 24px 40px;
}
.security-dashboard {
padding: 24px 40px;
gap: 28px;
}
.search-wrapper {
max-width: 480px;
}
.modal {
max-width: 520px;
}
.modal-settings {
max-width: 580px;
}
}
/* Mobile padding and layout overrides */
body.is-mobile .vault-main {
flex: 1;
overflow: hidden;
min-height: 0;
}
body.is-mobile .vault-header {
padding: 12px 16px;
gap: 8px;
}
body.is-mobile .vault-title {
font-size: 16px;
}
body.is-mobile .vault-list {
padding: 8px 12px 80px;
}
body.is-mobile .panel-body {
padding: 16px;
}
body.is-mobile .security-dashboard {
padding: 12px;
gap: 16px;
}
body.is-mobile .vault-toolbar {
gap: 8px;
}
body.is-mobile .search-wrapper {
max-width: none;
}
body.is-mobile .sort-wrapper {
display: none;
}
body.is-mobile .vault-header .btn-fab {
display: none;
}
body.is-mobile .form-row {
flex-direction: column;
gap: 0;
}
body.is-mobile .modal-actions {
flex-wrap: wrap;
}
body.is-mobile .modal-overlay {
padding: 0;
align-items: flex-end;
}
body.is-mobile .modal {
max-width: 100%;
max-height: calc(var(--vh, 1vh) * 92);
border-radius: 16px 16px 0 0;
padding: 20px 16px;
}
body.is-mobile .security-summary {
padding: 16px;
gap: 12px;
flex-wrap: wrap;
}
body.is-mobile .sec-score {
font-size: 40px;
}
body.is-mobile .sec-stats {
gap: 8px;
flex-wrap: wrap;
}
body.is-mobile .sec-stat {
padding: 8px 12px;
min-width: 68px;
}
body.is-mobile .sec-stat-num {
font-size: 22px;
}
body.is-mobile .detail-field {
flex-direction: column;
gap: 4px;
}
body.is-mobile .detail-label {
width: auto;
}
/* Auth page — phone */
@media (max-width: 480px) {
.auth-container {
padding: 12px;
}
.auth-card {
padding: 28px 20px;
border-radius: 12px;
}
.auth-title {
font-size: 20px;
}
}
/* iOS input font-size — prevent auto-zoom on focus */
body.is-mobile .form-group input,
body.is-mobile .form-group select,
body.is-mobile .form-group textarea {
font-size: 16px;
}
/* ── Settings — new sections (Phase 6) ──────────────────────────── */
.settings-section-danger {
border-top: 2px solid var(--color-danger, #e53e3e);
margin-top: 8px;
padding-top: 16px;
}
.settings-section-danger .settings-section-title {
color: var(--color-danger, #e53e3e);
}
.btn-danger {
background: var(--color-danger, #e53e3e);
color: #fff;
border: none;
border-radius: var(--radius);
padding: 8px 18px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: opacity 0.15s;
}
.btn-danger:hover {
opacity: 0.85;
}
.btn-danger:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.recovery-code-box {
display: flex;
align-items: center;
gap: 8px;
background: var(--color-bg-secondary, #f7f7f7);
border: 1px solid var(--color-border);
border-radius: var(--radius);
padding: 12px 14px;
margin: 8px 0;
}
.recovery-code-box code {
font-family: monospace;
font-size: 15px;
letter-spacing: 0.08em;
word-break: break-all;
flex: 1;
}
.warning-text {
color: var(--color-danger, #e53e3e);
font-size: 13px;
font-weight: 500;
}
/* ── CSP-safe replacements for former inline styles ─────────────────── */
/* TOTP timer: turns red when ≤5 s remain (toggled via JS class) */
.totp-timer--expiring {
color: #dc2626;
}
/* Generate-password button injected below password field */
.btn-generate {
margin-top: 6px;
}
/* Scroll-lock applied to <html> when mobile sidebar is open */
html.sidebar-open {
overflow: hidden;
}
/* Password Generator modal max-width */
.gen-modal-inner {
max-width: 420px;
}
/* Generator length slider */
#gen-length {
width: 100%;
margin-top: 4px;
}
/* Generator options row */
.gen-options {
display: flex;
gap: 16px;
flex-wrap: wrap;
margin-bottom: 16px;
}
/* Generator form-group spacing */
.gen-form-group {
margin-bottom: 8px;
}
/* Generator input-with-toggle wrapper */
.gen-input-wrap {
margin-bottom: 4px;
}
/* Generator copy button */
#btn-gen-copy {
font-size: 14px;
}
/* Generator output field */
#gen-output {
padding-right: 40px;
}
/* Form field label — "optional" badge */
.field-label-optional {
font-size: 11px;
color: #9ca3af;
font-weight: 400;
}
/* Form field hint text below inputs */
.field-hint {
font-size: 11px;
color: #6b7280;
margin-top: 4px;
}
/* Monospace input — used for recovery codes and other token fields */
.input-monospace {
font-family: monospace;
letter-spacing: 0.05em;
}