eternalibre-notes/static/style.css
2026-09-29 16:34:15 -04:00

1150 lines
22 KiB
CSS

/* EternaLibre Notes — Apple Elegance meets Classic Desktop GUI */
* {
box-sizing: border-box;
}
html, body {
margin: 0;
padding: 0;
height: 100%;
font-family: -apple-system, BlinkMacSystemFont, "SF Pro", "Inter", "Segoe UI", Roboto, sans-serif;
background-color: var(--app-bg);
color: var(--app-fg);
transition: background-color 0.15s ease, color 0.15s ease;
}
body {
display: flex;
flex-direction: column;
height: 100dvh;
overflow: hidden;
}
a {
color: var(--ansi-4);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
button, input, select, textarea {
font: inherit;
color: inherit;
}
/* Menu Bar */
.menu-bar {
display: flex;
align-items: center;
justify-content: space-between;
height: 32px;
padding: 0 12px;
background-color: var(--app-bg);
border-bottom: 1px solid var(--ui-border);
user-select: none;
flex-shrink: 0;
transition: background-color 0.15s ease, border-color 0.15s ease;
}
.menu-list {
display: flex;
list-style: none;
margin: 0;
padding: 0;
gap: 4px;
}
.menu-item {
position: relative;
padding: 4px 10px;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
font-weight: 500;
}
/* Menus are opened by click (see `openMenu` in app.js), not CSS hover, so
that only one can be open at a time. `.open` both highlights the trigger
and reveals its dropdown. */
.menu-item:hover,
.menu-item:focus,
.menu-item.open {
background-color: var(--ui-hover);
outline: none;
}
.menu-item.open .menu-dropdown {
display: block;
}
.menu-dropdown {
display: none;
position: absolute;
top: calc(100% - 4px);
left: 0;
min-width: 180px;
/* The Theme menu has 40 entries; cap the height so it cannot run off
the bottom of the viewport (body has overflow: hidden). */
max-height: calc(100dvh - 48px);
overflow-y: auto;
-webkit-overflow-scrolling: touch;
overscroll-behavior: contain;
background-color: var(--app-bg);
border: 1px solid var(--ui-border);
border-radius: 6px;
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25);
list-style: none;
margin: 0;
padding: 12px 6px 6px;
z-index: 100;
}
/* The File menu is short, so let its flyout escape horizontally instead of
letting the generic scroll container clip it. The nested flyout handles its
own vertical scrolling near the bottom edge of the viewport. */
.menu-dropdown.file-menu-dropdown {
max-height: none;
overflow: visible;
}
.menu-dropdown li {
padding: 0;
}
.menu-dropdown button,
.menu-dropdown a {
display: block;
width: 100%;
padding: 6px 10px;
background: none;
border: none;
text-align: left;
color: var(--app-fg);
border-radius: 4px;
cursor: pointer;
font-size: 13px;
}
.menu-dropdown button:hover,
.menu-dropdown a:hover {
background-color: var(--ui-hover);
text-decoration: none;
}
/* Marks a View menu item whose pane is currently collapsed, so the state is
readable at a glance instead of only via the Hide/Show label. */
.menu-dropdown button.active-view::before {
content: "\2713\00a0";
opacity: 0.7;
}
.menu-dropdown hr {
border: none;
border-top: 1px solid var(--ui-border);
margin: 6px 0;
}
.menu-submenu-trigger {
display: flex !important;
align-items: center;
justify-content: space-between;
gap: 16px;
}
.menu-submenu-trigger:disabled {
opacity: 0.45;
cursor: not-allowed;
}
.menu-dropdown .menu-submenu-trigger:disabled:hover {
background: none;
}
.submenu-chevron {
font-size: 18px;
line-height: 1;
opacity: 0.65;
}
.menu-submenu {
position: relative;
}
.menu-submenu-list {
display: none;
position: absolute;
top: 0;
left: calc(100% - 2px);
z-index: 101;
min-width: 180px;
max-height: calc(100dvh - 48px);
overflow-y: auto;
-webkit-overflow-scrolling: touch;
overscroll-behavior: contain;
background-color: var(--app-bg);
border: 1px solid var(--ui-border);
border-radius: 6px;
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25);
list-style: none;
margin: 0;
padding: 6px;
}
.menu-submenu-list.open {
display: block;
}
.menu-dropdown .menu-submenu-list a {
padding-left: 10px;
font-size: 12px;
}
.app-title {
font-size: 12px;
font-weight: 600;
opacity: 0.7;
}
/* Toolbar */
.toolbar {
display: flex;
align-items: center;
justify-content: space-between;
height: 44px;
padding: 0 12px;
background-color: var(--app-bg);
border-bottom: 1px solid var(--ui-border);
gap: 12px;
flex-shrink: 0;
transition: background-color 0.15s ease, border-color 0.15s ease;
}
.toolbar-group {
display: flex;
align-items: center;
gap: 8px;
}
.btn {
padding: 5px 12px;
background-color: var(--ui-hover);
border: 1px solid var(--ui-border);
border-radius: 6px;
color: var(--app-fg);
cursor: pointer;
font-size: 13px;
font-weight: 500;
transition: background-color 0.15s ease, border-color 0.15s ease;
}
.btn:hover {
background-color: var(--ansi-12);
border-color: var(--app-fg);
}
.btn-primary {
background-color: var(--btn-primary-bg);
color: var(--btn-primary-fg);
border-color: var(--btn-primary-bg);
}
.btn-primary:hover {
background-color: var(--btn-primary-bg);
box-shadow: inset 0 0 0 999px rgba(255, 255, 255, 0.12);
}
.btn-danger {
background-color: var(--btn-danger-bg);
color: var(--btn-danger-fg);
border-color: var(--btn-danger-bg);
}
.btn-danger:hover {
background-color: var(--btn-danger-bg);
box-shadow: inset 0 0 0 999px rgba(255, 255, 255, 0.12);
}
.search-form input,
.theme-form select {
padding: 5px 10px;
background-color: var(--app-bg);
border: 1px solid var(--ui-border);
border-radius: 6px;
font-size: 13px;
outline: none;
}
.search-form input:focus,
.theme-form select:focus {
border-color: var(--ansi-4);
box-shadow: 0 0 0 2px var(--ui-hover);
}
.theme-form {
display: flex;
align-items: center;
gap: 6px;
font-size: 13px;
}
/* Flash Messages */
.flash-message {
padding: 8px 12px;
background-color: var(--ansi-2);
color: var(--app-bg);
font-size: 13px;
font-weight: 500;
flex-shrink: 0;
}
/* App Body */
.app-body {
display: grid;
grid-template-columns: 280px 1fr 1fr;
flex: 1;
min-height: 0;
overflow: hidden;
}
/* When a pane is collapsed via the View menu, the remaining pane reclaims the
space. Each state sets its own column count so a lone pane always spans the
full remaining width instead of sitting in a half-width track.
Scoped to desktop only: on mobile the tab bar already decides which single
pane is shown, and these rules would outrank `.app-body > .mobile-active`
on specificity, leaving the Preview tab permanently empty. */
@media (min-width: 901px) {
.app-body.hide-preview,
.app-body.hide-editor {
grid-template-columns: 280px 1fr;
}
.app-body.hide-editor.hide-preview {
grid-template-columns: 280px;
}
}
@media (min-width: 601px) and (max-width: 900px) {
.app-body.hide-preview,
.app-body.hide-editor {
grid-template-columns: 240px 1fr;
}
.app-body.hide-editor.hide-preview {
grid-template-columns: 240px;
}
}
@media (min-width: 601px) {
.app-body.hide-editor .editor-pane {
display: none;
}
.app-body.hide-preview .preview-pane {
display: none;
}
}
/* Sidebar */
.sidebar {
background-color: var(--app-bg);
border-right: 1px solid var(--ui-border);
overflow-y: auto;
display: flex;
flex-direction: column;
transition: background-color 0.15s ease, border-color 0.15s ease;
}
.sidebar-header {
padding: 12px 16px;
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
opacity: 0.6;
border-bottom: 1px solid var(--ui-border);
}
.note-list {
list-style: none;
margin: 0;
padding: 8px;
display: flex;
flex-direction: column;
gap: 4px;
}
.note-item {
border-radius: 8px;
transition: background-color 0.15s ease;
}
.note-item:hover {
background-color: var(--ui-hover);
}
.note-item.active {
background-color: var(--ui-hover);
}
.note-item a {
display: block;
padding: 10px 12px;
color: inherit;
}
.note-item a:hover {
text-decoration: none;
}
.note-title {
font-size: 14px;
font-weight: 600;
margin-bottom: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.note-meta {
font-size: 11px;
opacity: 0.6;
margin-bottom: 4px;
}
.note-preview {
font-size: 12px;
opacity: 0.7;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.note-item.empty {
padding: 20px;
text-align: center;
opacity: 0.5;
font-size: 13px;
}
/* Editor Pane */
.editor-pane {
display: flex;
flex-direction: column;
background-color: var(--app-bg);
border-right: 1px solid var(--ui-border);
overflow: hidden;
transition: background-color 0.15s ease, border-color 0.15s ease;
}
.note-form {
display: flex;
flex-direction: column;
height: 100%;
}
.note-title-input {
padding: 14px 16px;
font-size: 20px;
font-weight: 700;
background: transparent;
border: none;
border-bottom: 1px solid var(--ui-border);
outline: none;
color: var(--app-fg);
}
.note-title-input:focus {
border-bottom-color: var(--ansi-4);
}
.note-content-input {
flex: 1;
width: 100%;
padding: 16px;
font-family: "SF Mono", "Monaco", "Inconsolata", "Fira Code", monospace;
font-size: 14px;
line-height: 1.6;
background: transparent;
border: none;
outline: none;
resize: none;
color: var(--app-fg);
tab-size: 2;
}
.form-actions {
padding: 10px 16px;
border-top: 1px solid var(--ui-border);
display: flex;
justify-content: flex-end;
}
.empty-state {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
opacity: 0.5;
font-size: 14px;
}
/* Preview Pane */
.preview-pane {
display: flex;
flex-direction: column;
background-color: var(--app-bg);
overflow-y: auto;
transition: background-color 0.15s ease;
}
.markdown-body {
flex: 1;
padding: 20px 24px;
font-size: 15px;
line-height: 1.7;
max-width: 100%;
}
.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
margin-top: 24px;
margin-bottom: 12px;
font-weight: 700;
line-height: 1.3;
}
.markdown-body h1 { font-size: 28px; border-bottom: 1px solid var(--ui-border); padding-bottom: 8px; }
.markdown-body h2 { font-size: 22px; border-bottom: 1px solid var(--ui-border); padding-bottom: 6px; }
.markdown-body h3 { font-size: 18px; }
.markdown-body h4 { font-size: 16px; }
.markdown-body p {
margin: 0 0 14px;
}
.markdown-body ul,
.markdown-body ol {
margin: 0 0 14px;
padding-left: 24px;
}
.markdown-body li {
margin-bottom: 4px;
}
.markdown-body blockquote {
margin: 0 0 14px;
padding: 8px 16px;
border-left: 4px solid var(--ansi-4);
background-color: var(--ui-hover);
border-radius: 0 6px 6px 0;
}
.markdown-body code {
font-family: "SF Mono", "Monaco", "Inconsolata", "Fira Code", monospace;
font-size: 13px;
padding: 2px 6px;
background-color: var(--ui-hover);
border-radius: 4px;
}
.markdown-body pre {
padding: 14px;
background-color: var(--ansi-0);
border-radius: 8px;
overflow-x: auto;
margin: 0 0 14px;
}
.markdown-body pre code {
background: transparent;
padding: 0;
}
.markdown-body table {
width: 100%;
border-collapse: collapse;
margin-bottom: 14px;
}
.markdown-body th,
.markdown-body td {
padding: 8px 12px;
border: 1px solid var(--ui-border);
text-align: left;
}
.markdown-body th {
background-color: var(--ui-hover);
font-weight: 600;
}
.markdown-body hr {
border: none;
border-top: 1px solid var(--ui-border);
margin: 20px 0;
}
.markdown-body input[type="checkbox"] {
margin-right: 8px;
}
/* Status Bar */
.status-bar {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 16px;
height: 24px;
padding: 0 12px;
background-color: var(--app-bg);
border-top: 1px solid var(--ui-border);
font-size: 11px;
opacity: 0.8;
flex-shrink: 0;
transition: background-color 0.15s ease, border-color 0.15s ease;
}
/* Auth Pages (/setup, /login) */
.auth-page {
display: flex;
align-items: center;
justify-content: center;
min-height: 100dvh;
padding: 24px 16px;
overflow-y: auto;
}
.auth-card {
width: 100%;
max-width: 420px;
padding: 32px 28px;
background-color: var(--app-bg);
border: 1px solid var(--ui-border);
border-radius: 12px;
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.25);
}
.auth-card h1 {
margin: 0 0 6px;
font-size: 22px;
font-weight: 700;
}
.auth-subtitle {
margin: 0 0 20px;
font-size: 14px;
line-height: 1.5;
opacity: 0.75;
}
.auth-error {
margin-bottom: 16px;
padding: 10px 12px;
border: 1px solid var(--ansi-1);
border-radius: 6px;
background-color: color-mix(in srgb, var(--ansi-1) 12%, transparent);
color: var(--ansi-9);
font-size: 13px;
line-height: 1.4;
}
.auth-qr {
display: flex;
justify-content: center;
margin-bottom: 18px;
padding: 12px;
background-color: #ffffff;
border-radius: 8px;
}
.auth-qr svg {
display: block;
width: 100%;
max-width: 240px;
height: auto;
}
.auth-manual {
margin-bottom: 16px;
text-align: center;
}
.auth-manual p {
margin: 0 0 6px;
font-size: 12px;
opacity: 0.7;
}
.auth-secret {
display: block;
padding: 8px 10px;
background-color: var(--ui-hover);
border: 1px solid var(--ui-border);
border-radius: 6px;
font-family: "SF Mono", "Monaco", "Inconsolata", "Fira Code", monospace;
font-size: 13px;
word-break: break-all;
user-select: all;
}
.auth-note {
margin: 0 0 18px;
font-size: 12px;
line-height: 1.5;
opacity: 0.65;
}
.auth-note code {
padding: 1px 5px;
background-color: var(--ui-hover);
border-radius: 4px;
font-size: 11px;
}
.auth-form {
display: flex;
flex-direction: column;
gap: 10px;
}
.auth-form label {
font-size: 13px;
font-weight: 500;
}
.auth-input {
width: 100%;
min-height: 52px;
padding: 12px 14px;
background-color: var(--app-bg);
border: 1px solid var(--ui-border);
border-radius: 8px;
color: var(--app-fg);
font-family: "SF Mono", "Monaco", "Inconsolata", "Fira Code", monospace;
font-size: 24px;
letter-spacing: 0.35em;
text-align: center;
outline: none;
transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.auth-input:focus {
border-color: var(--ansi-4);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--ansi-4) 25%, transparent);
}
.auth-form .btn {
min-height: 44px;
margin-top: 4px;
}
.auth-footer {
margin: 22px 0 0;
text-align: center;
font-size: 11px;
opacity: 0.5;
}
/* About Page */
.about-page {
display: flex;
flex-direction: column;
height: 100dvh;
}
.about-page main {
flex: 1;
padding: 40px;
overflow-y: auto;
}
/* The about page was the one screen in the app with no typography rules at
all, so it rendered at UA defaults: a 2em heading with 0.67em margins and
a 1.9 line-height feature list. That is what made it read as a desktop
page on a phone. */
.about-page main > h1 {
font-size: 26px;
line-height: 1.2;
margin: 0 0 4px;
/* Balances a wrapped title instead of leaving a ragged last line. */
text-wrap: balance;
}
.about-page main > p {
line-height: 1.6;
}
.about-page main > h2 {
font-size: 15px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
opacity: 0.6;
margin: 28px 0 10px;
}
/* Outbound links. Kept as a list rather than prose so each destination reads
as its own target, and so the mobile 44px touch-target rule can apply to
the whole row. */
.about-links {
list-style: none;
margin: 0 0 20px;
padding: 0;
}
.about-links li {
margin: 0;
}
.about-links a {
display: block;
padding: 8px 12px;
margin: 0 -12px;
border-radius: 6px;
font-size: 15px;
transition: background-color 0.15s ease;
}
.about-links a:hover {
background-color: var(--ui-hover);
text-decoration: none;
}
/* Sits last and stays muted; it is a colophon, not a call to action. */
.about-page .about-colophon {
font-size: 13px;
opacity: 0.7;
margin: 28px 0 20px;
}
.about-page .tagline {
font-size: 18px;
font-weight: 600;
color: var(--ansi-4);
margin-top: 0;
}
.about-features {
margin: 0 0 20px;
padding-left: 22px;
line-height: 1.9;
}
/* "Back to notes" is the only way off this screen, so it gets a real target
rather than bare link text. */
.about-page .back-link {
display: inline-block;
padding: 8px 14px;
border: 1px solid var(--ui-border);
border-radius: 6px;
font-size: 14px;
font-weight: 500;
transition: background-color 0.15s ease, border-color 0.15s ease;
}
.about-page .back-link:hover {
background-color: var(--ui-hover);
text-decoration: none;
}
/* Mobile Tab Bar — hidden on desktop */
.tab-bar {
display: none;
}
/* Responsive */
@media (max-width: 900px) {
.app-body {
grid-template-columns: 240px 1fr 1fr;
}
}
/* Mobile: <= 600px (covers iPhone 11 at 390px CSS width) */
@media (max-width: 600px) {
html, body {
height: 100%;
overscroll-behavior: none;
}
body {
height: 100dvh;
}
/* Desktop-only affordances, hidden once the tab bar takes over.
Paired with the `min-width: 601px` rules for the pane toggles above —
keep the two breakpoints in sync. */
.desktop-only {
display: none;
}
/* Menu bar wraps to a second row.
NOTE: do not use `overflow: auto` here — it would create a new stacking
context and trap the absolutely-positioned `.menu-dropdown` underneath
the toolbar and content panes. */
.menu-bar {
height: auto;
min-height: 40px;
padding: 0 8px;
flex-shrink: 0;
}
.menu-list {
flex-wrap: wrap;
overflow: visible;
}
.menu-item {
padding: 8px 10px;
min-height: 40px;
display: flex;
align-items: center;
white-space: nowrap;
font-size: 14px;
}
/* Constrain the dropdown to the viewport width and a conservative height
on phones. Safe to use `overflow` here: it only clips the dropdown's own
contents, it does not trap the dropdown inside an ancestor's context. */
.menu-dropdown {
max-width: calc(100vw - 16px);
max-height: 55dvh;
}
.menu-submenu-list {
width: calc(100vw - 205px);
min-width: 120px;
max-width: 180px;
max-height: calc(100dvh - 56px);
}
.app-title {
display: none;
}
/* Toolbar wraps instead of stretching off-screen */
.toolbar {
flex-wrap: wrap;
height: auto;
padding: 8px;
gap: 8px;
flex-shrink: 0;
}
.toolbar-group {
flex: 1 1 auto;
min-width: 0;
}
.search-form {
flex: 1 1 100%;
min-width: 0;
display: flex;
}
.search-form input {
flex: 1;
width: 100%;
min-width: 0;
}
.theme-form {
flex: 1 1 auto;
min-width: 0;
}
.theme-form select {
flex: 1;
min-width: 0;
}
/* Apple HIG minimum 44px touch targets */
.btn {
min-height: 44px;
padding: 10px 14px;
font-size: 15px;
flex-shrink: 0;
}
.search-form input,
.theme-form select {
min-height: 44px;
padding: 10px 12px;
font-size: 16px; /* prevents iOS zoom-on-focus */
}
/* Tab bar replaces the 3-column layout on mobile */
.tab-bar {
display: flex;
flex-shrink: 0;
background-color: var(--app-bg);
border-bottom: 1px solid var(--ui-border);
padding: 4px 8px;
gap: 4px;
transition: background-color 0.15s ease, border-color 0.15s ease;
}
.tab-button {
flex: 1;
min-height: 48px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2px;
padding: 6px 4px;
background: none;
border: none;
border-radius: 8px;
color: var(--app-fg);
font-size: 12px;
font-weight: 500;
cursor: pointer;
transition: background-color 0.15s ease, color 0.15s ease;
}
.tab-button.active {
background-color: var(--ui-hover);
}
.tab-icon {
font-size: 18px;
line-height: 1;
}
/* Single-pane view: only the active tab's pane is displayed */
.app-body {
display: block;
flex: 1;
min-height: 0;
overflow: hidden;
}
.app-body > * {
display: none;
height: 100%;
border-right: none;
border-bottom: none;
-webkit-overflow-scrolling: touch;
}
.app-body > .mobile-active {
display: flex;
flex-direction: column;
}
/* Sidebar fills the pane on mobile */
.sidebar-header {
padding: 12px 16px;
}
.note-list {
padding: 8px;
}
.note-item a {
padding: 14px 12px;
}
/* Editor fills the pane on mobile */
.note-title-input {
font-size: 18px;
padding: 14px 16px;
flex-shrink: 0;
}
.note-content-input {
font-size: 16px; /* prevents iOS zoom-on-focus */
padding: 16px;
}
.form-actions {
padding: 12px 16px;
flex-shrink: 0;
}
.form-actions .btn {
width: 100%;
}
/* Preview fills the pane on mobile */
.markdown-body {
padding: 20px 16px;
font-size: 16px;
}
/* Status bar stays compact */
.status-bar {
height: 28px;
flex-shrink: 0;
gap: 10px;
font-size: 11px;
overflow-x: auto;
scrollbar-width: none;
}
.status-bar::-webkit-scrollbar {
display: none;
}
/* Prevent horizontal page scroll */
.flash-message {
flex-shrink: 0;
}
/* About page. Without these it keeps the desktop reading measure: a 26px
heading, an 18px tagline, and a 1.9 line-height list, all sized for a
wide column rather than a phone. */
.about-page main {
padding: 20px 16px;
}
.about-page main > h1 {
font-size: 22px;
}
.about-page .tagline {
font-size: 16px;
}
.about-features {
padding-left: 20px;
line-height: 1.6;
}
/* Apple HIG minimum touch target, matching `.btn` above. The negative
margin is cancelled out so the hover background still bleeds to the
padding edge without pushing the row off the column. */
.about-links a {
min-height: 44px;
padding: 12px;
margin: 0 -4px;
font-size: 16px; /* prevents iOS zoom-on-focus */
}
.about-page main > h2 {
margin: 24px 0 8px;
}
.about-page .about-colophon {
font-size: 13px;
margin: 24px 0 20px;
}
/* Apple HIG minimum touch target, matching `.btn` above. */
.about-page .back-link {
display: block;
min-height: 44px;
padding: 12px 16px;
text-align: center;
font-size: 16px;
}
}