CSS Refactor

- Commonized a lot of core components
- Moved to a bit more of a design system
- Moved to BEM style naming
This commit is contained in:
Adam Brown 2025-12-24 01:15:21 -08:00
parent 07387a9db7
commit f82f3561bb
23 changed files with 2011 additions and 1720 deletions

926
docs/WEB-DESIGN-SYSTEM.md Normal file
View file

@ -0,0 +1,926 @@
# Hammer Editor Web Design System
This document describes the design system used for Hammer Editor's server-side web interface. The design follows a "
Writer's Desk" aesthetic with a unified component library using BEM naming conventions.
## Table of Contents
- [Design Philosophy](#design-philosophy)
- [CSS Architecture](#css-architecture)
- [Design Tokens](#design-tokens)
- [Typography](#typography)
- [Shared Animations](#shared-animations)
- [Layout Components](#layout-components)
- [UI Components](#ui-components)
- [Form Components](#form-components)
- [Utility Classes](#utility-classes)
- [Responsive Design](#responsive-design)
- [Accessibility](#accessibility)
---
## Design Philosophy
The Hammer web interface uses a **"Writer's Desk"** aesthetic that evokes:
- **Notebook paper** with lined backgrounds and red margin lines
- **Typewriter typography** for headings and labels
- **Warm, paper-like colors** with amber/gold accents
- **Sticky note aesthetics** for feature cards
- **Subtle shadows and textures** that feel tactile
The goal is to create an interface that feels familiar and comfortable to writers, while remaining modern and
functional.
---
## CSS Architecture
### File Structure
```
server/src/main/resources/assets/css/
├── base.css # Design tokens, typography, shared components
├── home.css # Landing/marketing page
├── dashboard.css # User dashboard
├── admin.css # Admin pages
├── login.css # Login page
├── author.css # Public author pages
├── story.css # Story viewing/editing pages
└── error.css # Error pages (404, 500, etc.)
```
### Naming Convention
We use **BEM (Block Element Modifier)** naming:
```css
.block {
}
/* Component block */
.block__element {
}
/* Child element */
.block--modifier {
}
/* Variant/state */
```
Examples:
- `.card` - Block (the component)
- `.card__title` - Element (part of the card)
- `.card--interactive` - Modifier (makes it clickable)
---
## Design Tokens
### Color Palette
```css
/* Primary Colors */
--primary: #2563eb
; /* Blue - links, primary actions */
--primary-dark: #1e40af
;
--primary-light: #3b82f6
;
/* Accent Colors (Amber/Gold) */
--accent: #d97706
; /* Main accent - borders, highlights */
--accent-light: #f59e0b
;
--accent-dark: #b45309
;
/* Neutral Scale (Stone) */
--neutral-50: #fafaf9
;
--neutral-100: #f5f5f4
;
--neutral-200: #e7e5e4
;
--neutral-300: #d6d3d1
;
--neutral-400: #a8a29e
;
--neutral-500: #78716c
;
--neutral-600: #57534e
;
--neutral-700: #44403c
;
--neutral-800: #292524
;
--neutral-900: #1c1917
;
/* Writer's Desk Colors */
--sticky-yellow: #fef08a
; /* Yellow sticky notes */
--sticky-blue: #bfdbfe
; /* Blue sticky notes */
--sticky-green: #bbf7d0
; /* Green sticky notes */
--sticky-pink: #fbcfe8
; /* Pink sticky notes */
--paper-white: #fffef9
; /* Warm paper color */
--ink: #1c1917
; /* Dark ink color */
/* Semantic Colors */
--success: #10b981
; /* Green - success states */
--warning: #f59e0b
; /* Amber - warnings */
--error: #ef4444
; /* Red - errors */
```
### Shadows
```css
--shadow-sm:
0
1
px
2
px
0
rgba
(
0
,
0
,
0
,
0.05
)
;
--shadow-md:
0
4
px
6
px
-
1
px
rgba
(
0
,
0
,
0
,
0.1
)
,
0
2
px
4
px
-
1
px
rgba
(
0
,
0
,
0
,
0.06
)
;
--shadow-lg:
0
10
px
15
px
-
3
px
rgba
(
0
,
0
,
0
,
0.1
)
,
0
4
px
6
px
-
2
px
rgba
(
0
,
0
,
0
,
0.05
)
;
--shadow-xl:
0
20
px
25
px
-
5
px
rgba
(
0
,
0
,
0
,
0.1
)
,
0
10
px
10
px
-
5
px
rgba
(
0
,
0
,
0
,
0.04
)
;
--shadow-sticky:
0
1
px
3
px
rgba
(
0
,
0
,
0
,
0.12
)
,
0
1
px
2
px
rgba
(
0
,
0
,
0
,
0.24
)
;
--shadow-sticky-hover:
0
10
px
20
px
rgba
(
0
,
0
,
0
,
0.15
)
,
0
3
px
6
px
rgba
(
0
,
0
,
0
,
0.10
)
;
```
### Animation Timing
```css
--transition-fast:
150
ms
cubic-bezier
(
0.4
,
0
,
0.2
,
1
)
;
--transition-base:
250
ms
cubic-bezier
(
0.4
,
0
,
0.2
,
1
)
;
--transition-slow:
350
ms
cubic-bezier
(
0.4
,
0
,
0.2
,
1
)
;
```
---
## Typography
### Font Families
1. **Kingthings Typewriter** - Headings, labels, buttons
- Evokes vintage typewriter aesthetic
- Used for all `h1-h6`, form labels, button text
2. **Lora** - Body text, paragraphs
- Elegant serif font for readability
- Used for body copy, descriptions, input values
### Usage
```css
/* Headings and UI elements */
font-family:
'Kingthings Typewriter'
,
monospace
;
/* Body text */
font-family:
'Lora'
,
Georgia, serif
;
```
### Responsive Font Sizes
```css
h1 {
font-size: clamp(2.5rem, 5vw, 4.5rem);
}
h2 {
font-size: clamp(2rem, 4vw, 3.5rem);
}
h3 {
font-size: clamp(1.5rem, 3vw, 2.25rem);
}
```
---
## Shared Animations
These animations are defined in `base.css` and can be used across all pages:
```css
/* Fade effects */
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
/* Slide effects */
@keyframes slideInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideInDown {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
```
Usage:
```css
.my-element {
animation: slideInUp 0.5s ease-out;
}
```
---
## Layout Components
### Page Container
The base wrapper for page content.
```html
<main class="page">
<div class="page__container">
<!-- Page content -->
</div>
</main>
```
**Modifiers:**
- `.page--centered` - Centers content vertically and horizontally
- `.page__container--narrow` - Max-width 480px (forms, login)
- `.page__container--wide` - Max-width 1200px (admin, dashboards)
**Page-specific modifiers:**
- `.page__container--author` - Max-width 900px
- `.page__container--error` - Max-width 600px
### Page Header
Dark header bar with title, subtitle, and optional actions.
```html
<div class="page-header">
<div class="page-header__content">
<h1 class="page-header__title">Dashboard</h1>
<p class="page-header__subtitle">Welcome back, username</p>
</div>
<div class="page-header__actions">
<button class="btn btn--ghost">Settings</button>
</div>
</div>
```
### Paper Section
Notebook-style content sections with lined background and red margin.
```html
<div class="paper-section">
<div class="section-header">
<h2 class="section-header__title">Your Projects</h2>
<div class="section-header__actions">
<button class="btn">New Project</button>
</div>
</div>
<p class="section-description">Manage your writing projects.</p>
<!-- Section content -->
</div>
```
**Modifiers:**
- `.paper-section--compact` - Reduced padding
- `.paper-section--centered` - Center-aligned text
---
## UI Components
### Cards
Interactive card components for lists of items.
```html
<div class="card-list">
<a href="/project/1" class="card card--interactive">
<div class="card__info">
<h3 class="card__title">My Novel</h3>
<span class="card__subtitle">Last synced: 2 hours ago</span>
</div>
<i class="fa-solid fa-chevron-right card__arrow"></i>
</a>
</div>
```
**Modifiers:**
- `.card--interactive` - Adds hover effects (translateX, border change)
- `.card--gradient` - Gradient background
### Dialog/Modal
Modal dialogs with header, body, and actions.
```html
<div class="dialog-overlay" id="my-dialog">
<div class="dialog">
<div class="dialog__header">
<h3>Dialog Title</h3>
<button class="dialog__close">
<i class="fas fa-times"></i>
</button>
</div>
<div class="dialog__body">
<!-- Dialog content -->
</div>
<div class="dialog__actions">
<button class="btn btn--ghost">Cancel</button>
<button class="btn btn--accent">Confirm</button>
</div>
</div>
</div>
```
**Modifiers:**
- `.dialog--warning` - Warning variant with amber tint
**JavaScript:**
Add `.closing` class to `.dialog-overlay` to trigger fade-out animation.
### Buttons
```html
<!-- Default (outlined) -->
<button class="btn">Default</button>
<!-- Primary (filled dark, hover to accent) -->
<button class="btn btn--primary">Primary</button>
<!-- Accent (filled amber) -->
<button class="btn btn--accent">Accent</button>
<!-- Danger (red outlined/filled) -->
<button class="btn btn--danger">Delete</button>
<!-- Ghost (subtle) -->
<button class="btn btn--ghost">Cancel</button>
<!-- Full width -->
<button class="btn btn--primary btn--block">Submit</button>
```
### Pagination
```html
<div class="pagination">
<button class="pagination-button">&laquo; Previous</button>
<span class="pagination-info">Page 1 of 5</span>
<button class="pagination-button">Next &raquo;</button>
</div>
```
### Empty State
For displaying when lists have no content.
```html
<div class="empty-state">
<div class="empty-state__icon">📝</div>
<p>No projects yet. Start writing!</p>
</div>
```
### Toast Notifications
```html
<div id="toast-container">
<div class="toast toast-success">
<span class="toast-icon"></span>
<span class="toast-message">Changes saved successfully</span>
<button class="toast-dismiss">×</button>
</div>
</div>
```
**Variants:** `.toast-success`, `.toast-warning`, `.toast-error`, `.toast-info`
---
## Form Components
### Form Field
```html
<div class="form-field">
<label for="email" class="form-field__label">Email Address</label>
<input type="email" id="email" class="form-input" placeholder="you@example.com">
</div>
```
### Input Types
```html
<!-- Text Input -->
<input type="text" class="form-input">
<!-- Select Dropdown -->
<select class="form-select">
<option>Option 1</option>
<option>Option 2</option>
</select>
<!-- Textarea -->
<textarea class="form-textarea"></textarea>
```
### Form Field Label Variants
```html
<label class="form-field__label">Regular Label</label>
<label class="form-field__label form-field__label--small">Small Label</label>
```
---
## Utility Classes
```css
.hidden {
display: none !important;
}
```
---
## Responsive Design
The design system includes responsive breakpoints:
| Breakpoint | Width | Usage |
|------------|----------------|-----------------------|
| Large | > 1024px | Full desktop layout |
| Medium | 769px - 1024px | Tablet/narrow desktop |
| Small | 601px - 768px | Large mobile |
| X-Small | ≤ 600px | Mobile phones |
| XX-Small | ≤ 480px | Small phones |
### Behavior Changes
**768px and below:**
- Page headers stack vertically
- Paper sections reduce padding
- Cards don't transform on hover
- Dialogs use 95% width
**480px and below:**
- Smaller font sizes for titles
- Reduced spacing throughout
---
## Accessibility
### Reduced Motion
All animations respect `prefers-reduced-motion`:
```css
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
```
### Focus States
All interactive elements have visible focus states:
```css
*:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 3px;
}
```
### Color Contrast
- Text colors meet WCAG AA contrast requirements
- Interactive elements have clear hover/focus states
- Error states use both color and icons for accessibility
---
## Page-Specific Extensions
While the shared components cover most use cases, some pages have additional specific styles:
- **home.css** - Marketing page sticky notes, feature grid, download cards
- **dashboard.css** - Pen name section, danger zone, account info
- **admin.css** - Sidebar navigation, whitelist management
- **login.css** - Login header styling, error shake animation, whitelist notices
- **author.css** - Author header with avatar, story count
- **story.css** - Story content formatting, publish toggle, access list
- **error.css** - Colored sticky-note error cards with animations
---
## Example: Complete Page Structure
```html
{{> header}}
<main class="page">
<div class="page__container">
<!-- Dark header bar -->
<div class="page-header">
<div class="page-header__content">
<h1 class="page-header__title">My Page</h1>
<p class="page-header__subtitle">Subtitle text</p>
</div>
</div>
<!-- Content section -->
<div class="paper-section">
<div class="section-header">
<h2 class="section-header__title">Section Title</h2>
</div>
<!-- Card list -->
<div class="card-list">
<a href="#" class="card card--interactive">
<div class="card__info">
<h3 class="card__title">Item Title</h3>
<span class="card__subtitle">Subtitle</span>
</div>
<i class="fa-solid fa-chevron-right card__arrow"></i>
</a>
</div>
<!-- Or empty state -->
<div class="empty-state">
<p>No items yet.</p>
</div>
</div>
</div>
</main>
{{> footer}}
```

View file

@ -1,519 +1,4 @@
/* Admin Page Styles */
.admin-page {
min-height: calc(100vh - 200px);
padding: 2rem;
background: linear-gradient(135deg, var(--neutral-100) 0%, var(--neutral-50) 100%);
}
.admin-container {
max-width: 1000px;
margin: 0 auto;
animation: fadeIn 0.5s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
/* Admin Header */
.admin-header {
background: var(--neutral-900);
color: white;
padding: 2.5rem 3rem;
border-radius: 0.75rem;
margin-bottom: 2rem;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: var(--shadow-lg);
border-bottom: 3px solid var(--accent);
animation: slideInDown 0.5s ease-out;
}
@keyframes slideInDown {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.admin-header-content h1 {
font-family: 'Kingthings Typewriter', monospace;
font-size: 2.5rem;
color: white;
margin-bottom: 0.5rem;
}
.admin-subtitle {
color: var(--neutral-700);
font-size: 1.0625rem;
font-style: italic;
margin: 0;
}
.logout-form {
margin: 0;
}
/* Section Cards */
.admin-section {
background: var(--surface);
background-image: repeating-linear-gradient(
transparent,
transparent 31px,
rgba(0, 0, 0, 0.03) 31px,
rgba(0, 0, 0, 0.03) 32px
);
padding: 2.5rem 3rem 2.5rem 4rem;
margin-bottom: 2rem;
border-radius: 0.75rem;
box-shadow: var(--shadow-lg),
inset 0 0 0 1px rgba(0, 0, 0, 0.05);
border-left: 4px solid var(--accent);
position: relative;
animation: slideInUp 0.5s ease-out;
animation-fill-mode: both;
}
.admin-section:nth-child(2) {
animation-delay: 0.1s;
}
.admin-section:nth-child(3) {
animation-delay: 0.2s;
}
@keyframes slideInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Red margin line like notebook paper */
.admin-section::before {
content: '';
position: absolute;
left: 2.5rem;
top: 0;
bottom: 0;
width: 2px;
background: rgba(239, 68, 68, 0.2);
pointer-events: none;
}
/* Section Headers */
.section-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 1rem;
margin-bottom: 1rem;
}
.section-title-group {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.section-header h2 {
font-family: 'Kingthings Typewriter', monospace;
font-size: 2rem;
color: var(--text-primary);
margin: 0;
}
.section-header-actions {
display: flex;
align-items: center;
gap: 1rem;
}
.toggle-button {
font-family: 'Kingthings Typewriter', monospace;
font-size: 1rem;
padding: 0.75rem 1.5rem;
background: var(--accent);
color: white;
border: none;
border-radius: 0.5rem;
cursor: pointer;
transition: all var(--transition-base);
box-shadow: var(--shadow-sm);
white-space: nowrap;
}
.toggle-button:hover {
background: var(--accent-dark);
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.toggle-button.active {
background: rgba(239, 68, 68, 0.9);
}
.toggle-button.active:hover {
background: #dc2626;
}
.section-badge {
font-family: 'Kingthings Typewriter', monospace;
font-size: 0.875rem;
padding: 0.375rem 0.875rem;
background: var(--neutral-200);
color: var(--neutral-700);
border-radius: 0.375rem;
font-weight: normal;
}
.section-badge.accent {
background: var(--accent-light);
color: var(--accent-dark);
}
.section-description {
color: var(--text-secondary);
font-size: 1rem;
margin-bottom: 1.5rem;
line-height: 1.6;
}
/* Message Display */
.message-display {
background: var(--neutral-100);
border: 2px solid var(--neutral-300);
border-radius: 0.5rem;
padding: 1.5rem;
margin-top: 1rem;
}
.message-display pre {
font-family: 'Courier New', monospace;
font-size: 0.9375rem;
color: var(--text-primary);
white-space: pre-wrap;
line-height: 1.6;
margin: 0;
}
/* Whitelist Form */
.whitelist-form {
margin: 1.5rem 0;
padding: 1.5rem;
background: rgba(217, 119, 6, 0.05);
border: 2px solid rgba(217, 119, 6, 0.2);
border-radius: 0.5rem;
}
.form-group {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.form-group label {
font-family: 'Kingthings Typewriter', monospace;
font-size: 1.125rem;
color: var(--text-primary);
}
.form-input-group {
display: flex;
gap: 0.75rem;
}
.form-input-group input {
flex: 1;
font-family: 'Lora', Georgia, serif;
font-size: 1rem;
padding: 0.875rem 1rem;
border: 2px solid var(--neutral-300);
border-radius: 0.5rem;
background: white;
color: var(--text-primary);
transition: all var(--transition-base);
box-shadow: var(--shadow-sm);
}
.form-input-group input::placeholder {
color: var(--neutral-400);
font-style: italic;
}
.form-input-group input:hover {
border-color: var(--neutral-400);
}
.form-input-group input:focus {
outline: none;
border-color: var(--accent);
box-shadow: var(--shadow-md),
0 0 0 3px rgba(217, 119, 6, 0.1);
}
.add-button,
.save-button {
font-family: 'Kingthings Typewriter', monospace;
font-size: 1rem;
padding: 0.875rem 2rem;
background: var(--accent);
color: white;
border: none;
border-radius: 0.5rem;
cursor: pointer;
transition: all var(--transition-base);
box-shadow: var(--shadow-sm);
white-space: nowrap;
}
.add-button:hover,
.save-button:hover {
background: var(--accent-dark);
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.add-button:active,
.save-button:active {
transform: translateY(0);
}
.settings-form {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.settings-select {
width: 100%;
font-family: 'Lora', Georgia, serif;
font-size: 1rem;
padding: 0.875rem 1rem;
border: 2px solid var(--neutral-300);
border-radius: 0.5rem;
background: white;
color: var(--text-primary);
transition: all var(--transition-base);
box-shadow: var(--shadow-sm);
cursor: pointer;
}
.settings-select:focus {
outline: none;
border-color: var(--accent);
box-shadow: var(--shadow-md),
0 0 0 3px rgba(217, 119, 6, 0.1);
}
.settings-textarea {
width: 100%;
font-family: 'Lora', Georgia, serif;
font-size: 1rem;
padding: 0.875rem 1rem;
border: 2px solid var(--neutral-300);
border-radius: 0.5rem;
background: white;
color: var(--text-primary);
transition: all var(--transition-base);
box-shadow: var(--shadow-sm);
resize: vertical;
min-height: 100px;
}
.settings-textarea:focus {
outline: none;
border-color: var(--accent);
box-shadow: var(--shadow-md),
0 0 0 3px rgba(217, 119, 6, 0.1);
}
.form-actions {
display: flex;
justify-content: flex-end;
}
/* Whitelist Container */
.whitelist-container {
margin-top: 2rem;
}
/* Whitelist User Items (for HTMX loaded content) */
#whitelist {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.whitelist-user {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 1.25rem;
background: white;
border: 2px solid var(--neutral-200);
border-radius: 0.5rem;
transition: all var(--transition-base);
box-shadow: var(--shadow-sm);
}
.whitelist-user:hover {
border-color: var(--neutral-300);
box-shadow: var(--shadow-md);
transform: translateX(4px);
}
.whitelist-user-email {
font-family: 'Lora', Georgia, serif;
font-size: 1rem;
color: var(--text-primary);
font-weight: 500;
}
.whitelist-user-actions {
display: flex;
gap: 0.5rem;
}
.remove-button {
font-family: 'Kingthings Typewriter', monospace;
font-size: 0.875rem;
padding: 0.5rem 1rem;
background: transparent;
color: #dc2626;
border: 2px solid #dc2626;
border-radius: 0.375rem;
cursor: pointer;
transition: all var(--transition-base);
}
.remove-button:hover {
background: #dc2626;
color: white;
transform: translateY(-1px);
box-shadow: var(--shadow-sm);
}
.remove-button:active {
transform: translateY(0);
}
/* Empty State */
.whitelist-empty {
text-align: center;
padding: 3rem 2rem;
color: var(--text-secondary);
font-style: italic;
}
.whitelist-empty::before {
content: '📝';
display: block;
font-size: 3rem;
margin-bottom: 1rem;
opacity: 0.5;
}
/* Responsive Design */
@media only screen and (max-width: 768px) {
.admin-page {
padding: 1.5rem;
}
.admin-header {
flex-direction: column;
gap: 1.5rem;
padding: 2rem 1.75rem;
text-align: center;
}
.admin-header-content h1 {
font-size: 2rem;
}
.admin-section {
padding: 2rem 1.75rem 2rem 3rem;
}
.admin-section::before {
left: 1.5rem;
}
.section-header {
flex-direction: column;
align-items: flex-start;
gap: 0.5rem;
}
.section-header h2 {
font-size: 1.75rem;
}
.form-input-group {
flex-direction: column;
}
.add-button {
width: 100%;
}
.whitelist-user {
flex-direction: column;
align-items: flex-start;
gap: 1rem;
}
.whitelist-user-actions {
width: 100%;
}
.remove-button {
flex: 1;
}
}
@media only screen and (max-width: 480px) {
.admin-header-content h1 {
font-size: 1.75rem;
}
.admin-subtitle {
font-size: 0.9375rem;
}
.section-header h2 {
font-size: 1.5rem;
}
}
/* Accessibility */
@media (prefers-reduced-motion: reduce) {
.admin-container,
.admin-header,
.admin-section {
animation: none;
}
.whitelist-user:hover {
transform: none;
}
}
/* Admin Page Styles - Page-specific overrides */
/* ========================================
Admin Layout with Sidebar
@ -588,32 +73,187 @@
min-width: 0;
}
/* Header actions for whitelist page */
.admin-header-actions {
/* ========================================
Admin-specific Buttons
======================================== */
.toggle-button {
font-family: 'Kingthings Typewriter', monospace;
font-size: 1rem;
padding: 0.75rem 1.5rem;
background: var(--accent);
color: white;
border: none;
border-radius: 0.5rem;
cursor: pointer;
transition: all var(--transition-base);
box-shadow: var(--shadow-sm);
white-space: nowrap;
}
.toggle-button:hover {
background: var(--accent-dark);
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.toggle-button.active {
background: rgba(239, 68, 68, 0.9);
}
.toggle-button.active:hover {
background: #dc2626;
}
.section-badge {
font-family: 'Kingthings Typewriter', monospace;
font-size: 0.875rem;
padding: 0.375rem 0.875rem;
background: var(--neutral-200);
color: var(--neutral-700);
border-radius: 0.375rem;
font-weight: normal;
}
.section-badge.accent {
background: var(--accent-light);
color: var(--accent-dark);
}
/* ========================================
Form Styling
======================================== */
.logout-form {
margin: 0;
}
.whitelist-form {
margin: 1.5rem 0;
padding: 1.5rem;
background: rgba(217, 119, 6, 0.05);
border: 2px solid rgba(217, 119, 6, 0.2);
border-radius: 0.5rem;
}
.form-group {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.form-input-group {
display: flex;
gap: 0.75rem;
}
.form-input-group .form-input {
flex: 1;
}
.add-button,
.save-button {
font-family: 'Kingthings Typewriter', monospace;
font-size: 1rem;
padding: 0.875rem 2rem;
background: var(--accent);
color: white;
border: none;
border-radius: 0.5rem;
cursor: pointer;
transition: all var(--transition-base);
box-shadow: var(--shadow-sm);
white-space: nowrap;
}
.add-button:hover,
.save-button:hover {
background: var(--accent-dark);
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.add-button:active,
.save-button:active {
transform: translateY(0);
}
.settings-form {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.form-actions {
display: flex;
justify-content: flex-end;
}
/* ========================================
Whitelist Items
======================================== */
.whitelist-container {
margin-top: 2rem;
}
#whitelist {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.whitelist-user {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
padding: 1rem 1.25rem;
background: white;
border: 2px solid var(--neutral-200);
border-radius: 0.5rem;
transition: all var(--transition-base);
box-shadow: var(--shadow-sm);
}
/* Placeholder styling for User Management */
.placeholder-content {
text-align: center;
padding: 4rem 2rem;
color: var(--text-tertiary);
.whitelist-user:hover {
border-color: var(--neutral-300);
box-shadow: var(--shadow-md);
transform: translateX(4px);
}
.placeholder-icon {
font-size: 4rem;
margin-bottom: 1.5rem;
opacity: 0.3;
}
.placeholder-content p {
.whitelist-user-email {
font-family: 'Lora', Georgia, serif;
font-size: 1.125rem;
font-style: italic;
max-width: 400px;
margin: 0 auto;
font-size: 1rem;
color: var(--text-primary);
font-weight: 500;
}
.whitelist-user-actions {
display: flex;
gap: 0.5rem;
}
.remove-button {
font-family: 'Kingthings Typewriter', monospace;
font-size: 0.875rem;
padding: 0.5rem 1rem;
background: transparent;
color: #dc2626;
border: 2px solid #dc2626;
border-radius: 0.375rem;
cursor: pointer;
transition: all var(--transition-base);
}
.remove-button:hover {
background: #dc2626;
color: white;
transform: translateY(-1px);
box-shadow: var(--shadow-sm);
}
.remove-button:active {
transform: translateY(0);
}
/* ========================================
@ -702,61 +342,10 @@
font-size: 0.75rem;
}
.users-empty {
text-align: center;
padding: 3rem 2rem;
color: var(--text-secondary);
font-style: italic;
}
/* ========================================
Responsive Design
======================================== */
.users-empty::before {
content: '\f0c0';
font-family: 'Font Awesome 6 Free';
font-weight: 900;
display: block;
font-size: 3rem;
margin-bottom: 1rem;
opacity: 0.3;
}
/* Pagination Styles */
.pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 1rem;
margin-top: 2rem;
padding-top: 1.5rem;
border-top: 2px solid var(--neutral-200);
}
.pagination-button {
font-family: 'Kingthings Typewriter', monospace;
font-size: 0.875rem;
padding: 0.5rem 1rem;
background: var(--neutral-100);
color: var(--text-primary);
border: 2px solid var(--neutral-300);
border-radius: 0.375rem;
cursor: pointer;
transition: all var(--transition-base);
}
.pagination-button:hover {
background: var(--accent);
color: white;
border-color: var(--accent);
transform: translateY(-1px);
box-shadow: var(--shadow-sm);
}
.pagination-info {
font-family: 'Lora', Georgia, serif;
font-size: 0.875rem;
color: var(--text-secondary);
}
/* Responsive: Stack on mobile */
@media only screen and (max-width: 900px) {
.admin-layout {
flex-direction: column;
@ -801,10 +390,29 @@
.admin-nav-link span {
font-size: 0.875rem;
}
}
.admin-header-actions {
@media only screen and (max-width: 768px) {
.form-input-group {
flex-direction: column;
gap: 0.75rem;
}
.add-button {
width: 100%;
}
.whitelist-user {
flex-direction: column;
align-items: flex-start;
gap: 1rem;
}
.whitelist-user-actions {
width: 100%;
}
.remove-button {
flex: 1;
}
.user-item {
@ -821,10 +429,12 @@
flex-direction: column;
gap: 0.5rem;
}
.pagination {
flex-direction: column;
gap: 0.75rem;
}
}
/* Accessibility */
@media (prefers-reduced-motion: reduce) {
.whitelist-user:hover,
.user-item:hover {
transform: none;
}
}

View file

@ -1,17 +1,9 @@
/* Author Page Styles */
/* A clean, simple literary portfolio */
.author-page {
min-height: calc(100vh - 200px);
padding: 2rem 1rem;
background: linear-gradient(135deg, var(--neutral-100) 0%, var(--neutral-50) 100%);
display: flex;
justify-content: center;
}
.author-container {
/* Author-specific container width */
.page__container--author {
max-width: 900px;
width: 100%;
}
/* ========================================
@ -26,11 +18,6 @@
animation: fadeIn 0.5s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* ========================================
Author Header (inside the card)
======================================== */
@ -85,75 +72,6 @@
font-style: italic;
}
/* ========================================
Stories List
======================================== */
.stories-list {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
/* ========================================
Story Card
======================================== */
.story-card {
display: flex;
align-items: center;
text-decoration: none;
background: linear-gradient(135deg, #fdfcfb 0%, #f5f4f0 100%);
border: 1px solid var(--neutral-200);
border-radius: 0.5rem;
box-shadow: var(--shadow-sm);
transition: all 0.2s ease;
padding: 1rem 1.25rem;
}
.story-card:hover {
background: white;
border-color: var(--accent);
box-shadow: var(--shadow-md);
}
.story-info {
flex: 1;
min-width: 0;
}
.story-name {
font-family: 'Kingthings Typewriter', monospace;
font-size: 1.125rem;
color: var(--text-primary);
margin: 0 0 0.25rem;
line-height: 1.3;
transition: color 0.2s ease;
}
.story-card:hover .story-name {
color: var(--accent-dark);
}
.story-date {
font-family: 'Lora', Georgia, serif;
font-size: 0.8125rem;
color: var(--text-secondary);
font-style: italic;
}
.story-arrow {
color: var(--neutral-400);
font-size: 0.875rem;
transition: all 0.2s ease;
margin-left: 1rem;
}
.story-card:hover .story-arrow {
color: var(--accent);
transform: translateX(3px);
}
/* ========================================
Story Count
======================================== */
@ -239,10 +157,6 @@
======================================== */
@media only screen and (max-width: 768px) {
.author-page {
padding: 1.5rem 1rem;
}
.stories-section {
padding: 1.5rem;
border-radius: 0.5rem;
@ -273,14 +187,6 @@
.author-name {
font-size: 1.5rem;
}
.story-card {
padding: 0.875rem 1rem;
}
.story-name {
font-size: 1rem;
}
}
/* ========================================
@ -288,16 +194,7 @@
======================================== */
@media (prefers-reduced-motion: reduce) {
.stories-section,
.story-card,
.read-link i {
.stories-section {
animation: none;
transition: none;
}
}
/* Focus styles for keyboard navigation */
.story-card:focus-visible {
outline: 3px solid var(--accent);
outline-offset: 2px;
}

View file

@ -549,3 +549,672 @@ a:focus-visible {
color: var(--text-secondary);
font-weight: 500;
}
/* ========================================
Shared Component System
BEM naming: .block__element--modifier
======================================== */
/* ----------------------------------------
Shared Animations
---------------------------------------- */
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes slideInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideInDown {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* ----------------------------------------
Page Layout System
---------------------------------------- */
.page {
min-height: calc(100vh - 200px);
padding: 2rem;
background: linear-gradient(135deg, var(--neutral-100) 0%, var(--neutral-50) 100%);
}
.page--centered {
display: flex;
align-items: center;
justify-content: center;
}
.page__container {
max-width: 1000px;
margin: 0 auto;
width: 100%;
animation: fadeIn 0.5s ease-out;
}
.page__container--narrow {
max-width: 480px;
}
.page__container--wide {
max-width: 1200px;
}
/* ----------------------------------------
Page Header (dark header bar)
---------------------------------------- */
.page-header {
background: var(--neutral-900);
color: white;
padding: 2.5rem 3rem;
border-radius: 0.75rem;
margin-bottom: 2rem;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: var(--shadow-lg);
border-bottom: 3px solid var(--accent);
animation: slideInDown 0.5s ease-out;
}
.page-header__content {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.page-header__title {
font-family: 'Kingthings Typewriter', monospace;
font-size: 2.5rem;
color: white;
margin: 0;
}
.page-header__subtitle {
color: var(--neutral-400);
font-size: 1.0625rem;
font-style: italic;
margin: 0;
}
.page-header__actions {
display: flex;
align-items: center;
gap: 1rem;
}
/* ----------------------------------------
Paper Section (notebook aesthetic)
---------------------------------------- */
.paper-section {
background: var(--surface);
background-image: repeating-linear-gradient(
transparent,
transparent 31px,
rgba(0, 0, 0, 0.03) 31px,
rgba(0, 0, 0, 0.03) 32px
);
padding: 2.5rem 3rem 2.5rem 4rem;
margin-bottom: 2rem;
border-radius: 0.75rem;
box-shadow: var(--shadow-lg),
inset 0 0 0 1px rgba(0, 0, 0, 0.05);
border-left: 4px solid var(--accent);
position: relative;
animation: slideInUp 0.5s ease-out;
animation-fill-mode: both;
}
/* Red margin line like notebook paper */
.paper-section::before {
content: '';
position: absolute;
left: 2.5rem;
top: 0;
bottom: 0;
width: 2px;
background: rgba(239, 68, 68, 0.2);
pointer-events: none;
}
.paper-section--compact {
padding: 3rem 2.5rem 3rem 4rem;
box-shadow: var(--shadow-xl),
inset 0 0 0 1px rgba(0, 0, 0, 0.05);
}
.paper-section--centered {
text-align: center;
}
/* Section Header */
.section-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 1rem;
margin-bottom: 1rem;
}
.section-header__title {
font-family: 'Kingthings Typewriter', monospace;
font-size: 2rem;
color: var(--text-primary);
margin: 0;
}
.section-header__actions {
display: flex;
align-items: center;
gap: 1rem;
}
.section-description {
color: var(--text-secondary);
font-size: 1rem;
margin-bottom: 1.5rem;
line-height: 1.6;
}
/* ----------------------------------------
Card System
---------------------------------------- */
.card {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.25rem 1.5rem;
background: white;
border: 2px solid var(--neutral-200);
border-radius: 0.5rem;
transition: all var(--transition-base);
box-shadow: var(--shadow-sm);
text-decoration: none;
color: inherit;
}
.card--interactive {
cursor: pointer;
}
.card--interactive:hover {
border-color: var(--accent);
box-shadow: var(--shadow-md);
transform: translateX(4px);
}
.card--gradient {
background: linear-gradient(135deg, #fdfcfb 0%, #f5f4f0 100%);
border: 1px solid var(--neutral-200);
}
.card--gradient:hover {
background: white;
border-color: var(--accent);
}
.card-list {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.card__info {
display: flex;
flex-direction: column;
gap: 0.375rem;
flex: 1;
min-width: 0;
}
.card__title {
font-family: 'Kingthings Typewriter', monospace;
font-size: 1.25rem;
color: var(--text-primary);
font-weight: normal;
margin: 0;
transition: color 0.2s ease;
}
.card--interactive:hover .card__title {
color: var(--accent-dark);
}
.card__subtitle {
font-family: 'Lora', Georgia, serif;
font-size: 0.875rem;
color: var(--text-secondary);
display: flex;
align-items: center;
gap: 0.5rem;
}
.card__arrow {
color: var(--neutral-400);
font-size: 0.875rem;
transition: all 0.2s ease;
margin-left: 1rem;
}
.card--interactive:hover .card__arrow {
color: var(--accent);
transform: translateX(3px);
}
/* ----------------------------------------
Dialog/Modal System
---------------------------------------- */
.dialog-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
animation: fadeIn 0.2s ease-out;
backdrop-filter: blur(2px);
}
.dialog-overlay.closing {
animation: fadeOut 0.2s ease-out forwards;
}
.dialog {
background: linear-gradient(135deg, rgba(255, 253, 250, 0.98) 0%, rgba(255, 250, 245, 1) 100%);
border: 2px solid var(--neutral-200);
border-radius: 0.75rem;
box-shadow: var(--shadow-xl),
0 0 0 1px rgba(0, 0, 0, 0.05);
max-width: 420px;
width: 90%;
animation: slideInUp 0.3s ease-out;
}
.dialog--warning {
background: linear-gradient(135deg, rgba(255, 253, 250, 0.99) 0%, rgba(255, 251, 235, 1) 100%);
border-color: var(--neutral-300);
max-width: 480px;
}
.dialog__header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1.25rem 1.5rem;
border-bottom: 1px dashed var(--neutral-200);
}
.dialog__header h3 {
font-family: 'Kingthings Typewriter', monospace;
font-size: 1.25rem;
color: var(--text-primary);
margin: 0;
}
.dialog__close {
display: flex;
align-items: center;
justify-content: center;
width: 2rem;
height: 2rem;
background: transparent;
border: none;
border-radius: 50%;
color: var(--text-secondary);
cursor: pointer;
transition: all var(--transition-base);
}
.dialog__close:hover {
background: var(--neutral-100);
color: var(--text-primary);
}
.dialog__body {
padding: 1.5rem;
}
.dialog__actions {
display: flex;
gap: 0.75rem;
justify-content: flex-end;
padding: 1rem 1.5rem;
background: rgba(255, 255, 255, 0.5);
border-top: 1px dashed var(--neutral-200);
}
/* ----------------------------------------
Button Variants
---------------------------------------- */
.btn--primary {
background: var(--neutral-900);
color: white;
border-color: var(--neutral-900);
}
.btn--primary:hover {
background: var(--accent);
border-color: var(--accent);
color: white;
}
.btn--accent {
background: var(--accent);
color: white;
border-color: var(--accent);
}
.btn--accent:hover {
background: var(--accent-dark);
border-color: var(--accent-dark);
color: white;
}
.btn--danger {
background: transparent;
color: #dc2626;
border-color: #dc2626;
}
.btn--danger:hover {
background: #dc2626;
color: white;
}
.btn--ghost {
background: transparent;
color: var(--text-secondary);
border-color: var(--neutral-300);
}
.btn--ghost:hover {
background: var(--neutral-100);
border-color: var(--neutral-400);
color: var(--text-primary);
}
.btn--block {
width: 100%;
justify-content: center;
}
/* ----------------------------------------
Form System
---------------------------------------- */
.form-field {
display: flex;
flex-direction: column;
gap: 0.5rem;
margin-bottom: 1.25rem;
}
.form-field:last-child {
margin-bottom: 0;
}
.form-field__label {
font-family: 'Kingthings Typewriter', monospace;
font-size: 1.125rem;
color: var(--text-primary);
letter-spacing: 0.01em;
}
.form-field__label--small {
font-size: 0.875rem;
color: var(--text-secondary);
}
.form-input {
font-family: 'Lora', Georgia, serif;
font-size: 1rem;
padding: 0.875rem 1rem;
border: 2px solid var(--neutral-300);
border-radius: 0.5rem;
background: white;
color: var(--text-primary);
transition: all var(--transition-base);
box-shadow: var(--shadow-sm);
}
.form-input::placeholder {
color: var(--neutral-400);
font-style: italic;
}
.form-input:hover {
border-color: var(--neutral-400);
}
.form-input:focus {
outline: none;
border-color: var(--accent);
box-shadow: var(--shadow-md),
0 0 0 3px rgba(217, 119, 6, 0.1);
}
.form-select {
font-family: 'Lora', Georgia, serif;
font-size: 1rem;
padding: 0.875rem 1rem;
border: 2px solid var(--neutral-300);
border-radius: 0.5rem;
background: white;
color: var(--text-primary);
transition: all var(--transition-base);
box-shadow: var(--shadow-sm);
cursor: pointer;
}
.form-select:hover {
border-color: var(--neutral-400);
}
.form-select:focus {
outline: none;
border-color: var(--accent);
box-shadow: var(--shadow-md),
0 0 0 3px rgba(217, 119, 6, 0.1);
}
.form-textarea {
font-family: 'Lora', Georgia, serif;
font-size: 1rem;
padding: 0.875rem 1rem;
border: 2px solid var(--neutral-300);
border-radius: 0.5rem;
background: white;
color: var(--text-primary);
transition: all var(--transition-base);
box-shadow: var(--shadow-sm);
resize: vertical;
min-height: 100px;
}
.form-textarea:hover {
border-color: var(--neutral-400);
}
.form-textarea:focus {
outline: none;
border-color: var(--accent);
box-shadow: var(--shadow-md),
0 0 0 3px rgba(217, 119, 6, 0.1);
}
/* ----------------------------------------
Utility Components
---------------------------------------- */
.empty-state {
text-align: center;
padding: 3rem 2rem;
color: var(--text-secondary);
font-style: italic;
}
.empty-state__icon {
font-size: 3rem;
margin-bottom: 1rem;
opacity: 0.5;
}
.info-panel {
background: linear-gradient(135deg, rgba(255, 253, 250, 0.95) 0%, rgba(255, 250, 245, 0.98) 100%);
border: 2px solid var(--neutral-200);
border-radius: 0.5rem;
overflow: hidden;
box-shadow: var(--shadow-sm);
margin-bottom: 1rem;
}
.info-panel__header {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.875rem 1.25rem;
background: rgba(255, 255, 255, 0.6);
border-bottom: 1px dashed var(--neutral-200);
}
.info-panel__content {
padding: 1rem 1.25rem;
}
.info-panel--collapsible .info-panel__header {
cursor: pointer;
transition: all var(--transition-base);
}
.info-panel--collapsible .info-panel__header:hover {
background: rgba(217, 119, 6, 0.05);
}
/* Hidden utility class */
.hidden {
display: none !important;
}
/* ----------------------------------------
Responsive Design for Shared Components
---------------------------------------- */
@media only screen and (max-width: 768px) {
.page {
padding: 1.5rem;
}
.page-header {
flex-direction: column;
gap: 1.5rem;
padding: 2rem 1.75rem;
text-align: center;
}
.page-header__title {
font-size: 2rem;
}
.page-header__actions {
flex-direction: column;
gap: 0.75rem;
width: 100%;
}
.paper-section {
padding: 2rem 1.75rem 2rem 3rem;
}
.paper-section::before {
left: 1.5rem;
}
.paper-section--compact {
padding: 2rem 1.75rem 2rem 3rem;
}
.paper-section--compact::before {
left: 1.5rem;
}
.section-header {
flex-direction: column;
align-items: flex-start;
gap: 0.5rem;
}
.section-header__title {
font-size: 1.75rem;
}
.card--interactive:hover {
transform: none;
}
.dialog {
width: 95%;
margin: 1rem;
}
}
@media only screen and (max-width: 600px) {
.page--centered {
padding: 1.5rem;
min-height: calc(100vh - 150px);
}
.paper-section--compact {
padding: 2rem 1.75rem 2rem 3rem;
}
}
@media only screen and (max-width: 480px) {
.page-header__title {
font-size: 1.75rem;
}
.page-header__subtitle {
font-size: 0.9375rem;
}
.section-header__title {
font-size: 1.5rem;
}
}

View file

@ -1,66 +1,6 @@
/* Dashboard Page Styles */
.dashboard-page {
min-height: calc(100vh - 200px);
padding: 2rem;
background: linear-gradient(135deg, var(--neutral-100) 0%, var(--neutral-50) 100%);
}
.dashboard-container {
max-width: 1000px;
margin: 0 auto;
animation: fadeIn 0.5s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
/* Dashboard Header */
.dashboard-header {
background: var(--neutral-900);
color: white;
padding: 2.5rem 3rem;
border-radius: 0.75rem;
margin-bottom: 2rem;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: var(--shadow-lg);
border-bottom: 3px solid var(--accent);
animation: slideInDown 0.5s ease-out;
}
@keyframes slideInDown {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.dashboard-header-content h1 {
font-family: 'Kingthings Typewriter', monospace;
font-size: 2.5rem;
color: white;
margin-bottom: 0.5rem;
}
.dashboard-subtitle {
color: var(--neutral-700);
font-size: 1.0625rem;
font-style: italic;
margin: 0;
}
/* Dashboard Page Styles - Page-specific overrides */
/* Dashboard-specific header buttons */
.logout-form {
margin: 0;
}
@ -89,85 +29,6 @@
transform: translateY(0);
}
/* Section Cards */
.dashboard-section {
background: var(--surface);
background-image: repeating-linear-gradient(
transparent,
transparent 31px,
rgba(0, 0, 0, 0.03) 31px,
rgba(0, 0, 0, 0.03) 32px
);
padding: 2.5rem 3rem 2.5rem 4rem;
margin-bottom: 2rem;
border-radius: 0.75rem;
box-shadow: var(--shadow-lg),
inset 0 0 0 1px rgba(0, 0, 0, 0.05);
border-left: 4px solid var(--accent);
position: relative;
animation: slideInUp 0.5s ease-out;
animation-fill-mode: both;
}
@keyframes slideInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Red margin line like notebook paper */
.dashboard-section::before {
content: '';
position: absolute;
left: 2.5rem;
top: 0;
bottom: 0;
width: 2px;
background: rgba(239, 68, 68, 0.2);
pointer-events: none;
}
/* Section Headers */
.section-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 1rem;
margin-bottom: 1rem;
}
.section-title-group {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.section-header h2 {
font-family: 'Kingthings Typewriter', monospace;
font-size: 2rem;
color: var(--text-primary);
margin: 0;
}
.section-description {
color: var(--text-secondary);
font-size: 1rem;
margin-bottom: 1.5rem;
line-height: 1.6;
}
/* Dashboard Header Actions */
.dashboard-header-actions {
display: flex;
align-items: center;
gap: 1rem;
}
.manage-server-button {
font-family: 'Kingthings Typewriter', monospace;
font-size: 1rem;
@ -189,14 +50,6 @@
box-shadow: var(--shadow-md);
}
.account-info p {
margin-bottom: 0.5rem;
}
.hidden {
display: none !important;
}
/* ========================================
Pen Name Section - Author Identity Claim
======================================== */
@ -760,81 +613,11 @@
box-shadow: none;
}
/* Projects List */
/* Projects and Account containers */
.projects-container {
margin-top: 1.5rem;
}
.projects-list {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.project-card {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.25rem 1.5rem;
background: white;
border: 2px solid var(--neutral-200);
border-radius: 0.5rem;
transition: all var(--transition-base);
box-shadow: var(--shadow-sm);
text-decoration: none;
color: inherit;
cursor: pointer;
}
.project-card:hover {
border-color: var(--accent);
box-shadow: var(--shadow-md);
transform: translateX(4px);
}
.project-info {
display: flex;
flex-direction: column;
gap: 0.375rem;
}
.project-name {
font-family: 'Kingthings Typewriter', monospace;
font-size: 1.25rem;
color: var(--text-primary);
font-weight: normal;
}
.project-sync-date {
font-family: 'Lora', Georgia, serif;
font-size: 0.875rem;
color: var(--text-secondary);
display: flex;
align-items: center;
gap: 0.5rem;
}
.project-arrow {
color: var(--neutral-400);
font-size: 0.875rem;
transition: all 0.2s ease;
margin-left: 1rem;
}
.project-card:hover .project-arrow {
color: var(--accent);
transform: translateX(3px);
}
/* Projects Empty State */
.projects-empty {
text-align: center;
padding: 3rem 2rem;
color: var(--text-secondary);
font-style: italic;
}
/* Account Info */
.account-container {
margin-top: 1.5rem;
}
@ -862,56 +645,11 @@
/* Responsive Design */
@media only screen and (max-width: 768px) {
.dashboard-page {
padding: 1.5rem;
}
.dashboard-header {
flex-direction: column;
gap: 1.5rem;
padding: 2rem 1.75rem;
text-align: center;
}
.dashboard-header-content h1 {
font-size: 2rem;
}
.dashboard-header-actions {
flex-direction: column;
gap: 0.75rem;
width: 100%;
}
.manage-server-button {
width: 100%;
text-align: center;
}
.dashboard-section {
padding: 2rem 1.75rem 2rem 3rem;
}
.dashboard-section::before {
left: 1.5rem;
}
.section-header {
flex-direction: column;
align-items: flex-start;
gap: 0.5rem;
}
.section-header h2 {
font-size: 1.75rem;
}
.project-card {
flex-direction: column;
align-items: flex-start;
gap: 0.75rem;
}
/* Pen Name Responsive */
.pen-name-section {
padding: 1rem;
@ -975,30 +713,12 @@
}
}
@media only screen and (max-width: 480px) {
.dashboard-header-content h1 {
font-size: 1.75rem;
}
.dashboard-subtitle {
font-size: 0.9375rem;
}
.section-header h2 {
font-size: 1.5rem;
}
}
/* Accessibility */
@media (prefers-reduced-motion: reduce) {
.dashboard-container,
.dashboard-header,
.dashboard-section,
.pen-name-edit {
animation: none;
}
.project-card:hover,
.claim-pen-name-btn:hover,
.set-pen-name-btn:hover:not(:disabled) {
transform: none;

View file

@ -1,16 +1,7 @@
/* Error Page Styles */
.error-page {
min-height: calc(100vh - 200px);
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
background: linear-gradient(135deg, var(--neutral-100) 0%, var(--neutral-50) 100%);
}
.error-container {
width: 100%;
/* Error-specific container width */
.page__container--error {
max-width: 600px;
}
@ -181,10 +172,6 @@
/* Responsive Design */
@media only screen and (max-width: 600px) {
.error-page {
padding: 1.5rem;
}
.error-card {
padding: 2.5rem 2rem;
transform: rotate(0deg);
@ -234,15 +221,6 @@
animation: fadeIn 0.3s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.error-card::before {
animation: none;
}

View file

@ -118,17 +118,6 @@
animation: slideInDown 0.4s ease-out;
}
@keyframes slideInDown {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.server-message h3 {
color: #92400e;
margin-bottom: 0.75rem;

View file

@ -1,59 +1,6 @@
/* Login Page Styles */
.login-page {
min-height: calc(100vh - 200px);
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
background: linear-gradient(135deg, var(--neutral-100) 0%, var(--neutral-50) 100%);
}
.login-container {
width: 100%;
max-width: 480px;
}
.login-card {
background: var(--surface);
background-image: repeating-linear-gradient(
transparent,
transparent 31px,
rgba(0, 0, 0, 0.03) 31px,
rgba(0, 0, 0, 0.03) 32px
);
padding: 3rem 2.5rem 3rem 4rem;
border-radius: 0.75rem;
box-shadow: var(--shadow-xl),
inset 0 0 0 1px rgba(0, 0, 0, 0.05);
border-left: 4px solid var(--accent);
position: relative;
animation: slideInUp 0.5s ease-out;
}
@keyframes slideInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Red margin line like notebook paper */
.login-card::before {
content: '';
position: absolute;
left: 2.5rem;
top: 0;
bottom: 0;
width: 2px;
background: rgba(239, 68, 68, 0.2);
pointer-events: none;
}
/* Login Page Styles - Page-specific overrides */
/* Login Header (centered within card) */
.login-header {
text-align: center;
margin-bottom: 2.5rem;
@ -73,58 +20,13 @@
margin: 0;
}
/* Form Styles */
/* Form Layout */
.login-form {
display: flex;
flex-direction: column;
gap: 1.75rem;
}
.form-field {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.form-field label {
font-family: 'Kingthings Typewriter', monospace;
font-size: 1.125rem;
color: var(--text-primary);
letter-spacing: 0.01em;
}
.form-field input {
font-family: 'Lora', Georgia, serif;
font-size: 1rem;
padding: 0.875rem 1rem;
border: 2px solid var(--neutral-300);
border-radius: 0.5rem;
background: white;
color: var(--text-primary);
transition: all var(--transition-base);
box-shadow: var(--shadow-sm);
}
.form-field input::placeholder {
color: var(--neutral-400);
font-style: italic;
}
.form-field input:hover {
border-color: var(--neutral-400);
}
.form-field input:focus {
outline: none;
border-color: var(--accent);
box-shadow: var(--shadow-md),
0 0 0 3px rgba(217, 119, 6, 0.1);
}
.form-field input:focus::placeholder {
color: var(--neutral-500);
}
/* Error/Message Styling */
.login-message {
background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%);
@ -153,7 +55,7 @@
font-size: 0.9375rem;
}
/* Button Styles */
/* Login Button (special styling with shimmer effect) */
.form-actions {
margin-top: 1rem;
}
@ -205,40 +107,6 @@
outline-offset: 4px;
}
/* Responsive Design */
@media only screen and (max-width: 600px) {
.login-page {
padding: 1.5rem;
min-height: calc(100vh - 150px);
}
.login-card {
padding: 2rem 1.75rem 2rem 3rem;
}
.login-card::before {
left: 1.5rem;
}
.login-header h1 {
font-size: 2rem;
}
.login-header p {
font-size: 0.9375rem;
}
.form-field input {
font-size: 1rem;
padding: 0.75rem 0.875rem;
}
.login-button {
font-size: 1rem;
padding: 0.875rem 1.75rem;
}
}
/* Login Info Section */
.login-info {
margin-top: 2rem;
@ -283,12 +151,24 @@
color: #78350f;
}
/* Accessibility */
@media (prefers-reduced-motion: reduce) {
.login-card {
animation: none;
/* Responsive Design */
@media only screen and (max-width: 600px) {
.login-header h1 {
font-size: 2rem;
}
.login-header p {
font-size: 0.9375rem;
}
.login-button {
font-size: 1rem;
padding: 0.875rem 1.75rem;
}
}
/* Accessibility */
@media (prefers-reduced-motion: reduce) {
.login-message {
animation: none;
}

View file

@ -167,15 +167,6 @@
}
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
/* ========================================
Story Info Panel
Displays story metadata at a glance
@ -703,175 +694,112 @@
}
/* ========================================
Share Dialog (Modal)
Dialog Customizations for Story Page
Additional styling for story-specific dialogs
======================================== */
.share-dialog-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
animation: fadeIn 0.2s ease-out;
backdrop-filter: blur(2px);
}
.share-dialog-overlay.closing {
animation: fadeOut 0.2s ease-out forwards;
}
@keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
.share-dialog {
background: linear-gradient(135deg, rgba(255, 253, 250, 0.98) 0%, rgba(255, 250, 245, 1) 100%);
border: 2px solid var(--neutral-200);
border-radius: 0.75rem;
box-shadow: var(--shadow-xl), 0 0 0 1px rgba(0, 0, 0, 0.05);
max-width: 420px;
width: 90%;
animation: slideInUp 0.3s ease-out;
}
@keyframes slideInUp {
from {
opacity: 0;
transform: translateY(20px) scale(0.95);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.share-dialog-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1.25rem 1.5rem;
border-bottom: 1px dashed var(--neutral-200);
}
.share-dialog-header h3 {
font-family: 'Kingthings Typewriter', monospace;
font-size: 1.25rem;
color: var(--text-primary);
margin: 0;
}
.dialog-close-btn {
display: flex;
align-items: center;
justify-content: center;
width: 2rem;
height: 2rem;
background: transparent;
border: none;
border-radius: 50%;
color: var(--text-secondary);
cursor: pointer;
transition: all var(--transition-base);
}
.dialog-close-btn:hover {
background: var(--neutral-100);
color: var(--text-primary);
}
.share-dialog-body {
padding: 1.5rem;
}
.share-dialog .form-field {
margin-bottom: 1.25rem;
}
.share-dialog .form-field:last-child {
margin-bottom: 0;
}
.share-dialog .form-field label {
display: block;
font-family: 'Kingthings Typewriter', monospace;
font-size: 0.875rem;
color: var(--text-secondary);
margin-bottom: 0.5rem;
}
.share-dialog .form-field .optional-label {
/* Optional label styling */
.optional-label {
font-style: italic;
color: var(--neutral-400);
}
.share-dialog .form-field input {
width: 100%;
padding: 0.75rem 1rem;
font-family: 'Lora', Georgia, serif;
font-size: 1rem;
color: var(--text-primary);
background: white;
border: 2px solid var(--neutral-200);
border-radius: 0.375rem;
box-sizing: border-box;
transition: border-color var(--transition-base);
/* Warning dialog header with icon */
.dialog__header--warning {
background: linear-gradient(135deg, rgba(251, 191, 36, 0.08) 0%, rgba(245, 158, 11, 0.05) 100%);
border-bottom: 1px dashed rgba(217, 119, 6, 0.25);
position: relative;
}
.share-dialog .form-field input:focus {
outline: none;
border-color: var(--accent);
.dialog__header--warning::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg,
transparent 0%,
rgba(245, 158, 11, 0.6) 20%,
rgba(217, 119, 6, 0.8) 50%,
rgba(245, 158, 11, 0.6) 80%,
transparent 100%
);
}
.share-dialog .form-field input::placeholder {
color: var(--neutral-400);
}
.share-dialog-actions {
.warning-icon-wrapper {
display: flex;
gap: 0.75rem;
justify-content: flex-end;
padding: 1rem 1.5rem;
background: rgba(255, 255, 255, 0.5);
border-top: 1px dashed var(--neutral-200);
align-items: center;
justify-content: center;
width: 2.5rem;
height: 2.5rem;
background: linear-gradient(135deg, rgba(251, 191, 36, 0.2) 0%, rgba(245, 158, 11, 0.15) 100%);
border: 1.5px solid rgba(217, 119, 6, 0.3);
border-radius: 50%;
flex-shrink: 0;
animation: warningPulse 2s ease-in-out infinite;
}
.share-dialog .cancel-btn {
padding: 0.625rem 1.25rem;
background: transparent;
color: var(--text-secondary);
border: 2px solid var(--neutral-300);
border-radius: 0.375rem;
font-family: 'Kingthings Typewriter', monospace;
@keyframes warningPulse {
0%, 100% {
box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.3);
}
50% {
box-shadow: 0 0 0 6px rgba(245, 158, 11, 0);
}
}
.warning-icon-wrapper i {
font-size: 1.125rem;
color: #b45309;
}
/* Warning Body Content */
.warning-text {
font-family: 'Lora', Georgia, serif;
font-size: 0.9375rem;
cursor: pointer;
transition: all var(--transition-base);
}
.share-dialog .cancel-btn:hover {
background: var(--neutral-100);
border-color: var(--neutral-400);
line-height: 1.7;
color: var(--text-primary);
margin: 0 0 1.25rem 0;
text-align: justify;
hyphens: auto;
}
.share-dialog .create-access-btn {
padding: 0.625rem 1.25rem;
background: linear-gradient(135deg, var(--accent) 0%, var(--accent-dark) 100%);
color: white;
border: 2px solid var(--accent-dark);
border-radius: 0.375rem;
/* Recommendation Callout */
.warning-recommendation {
display: flex;
align-items: flex-start;
gap: 0.75rem;
padding: 1rem 1.125rem;
background: linear-gradient(135deg, rgba(16, 185, 129, 0.06) 0%, rgba(5, 150, 105, 0.04) 100%);
border: 1.5px solid rgba(16, 185, 129, 0.2);
border-radius: 0.5rem;
border-left: 3px solid var(--success);
}
.warning-recommendation i {
font-size: 1rem;
color: var(--success);
margin-top: 0.125rem;
flex-shrink: 0;
}
.warning-recommendation span {
font-family: 'Kingthings Typewriter', monospace;
font-size: 0.9375rem;
font-weight: 500;
cursor: pointer;
transition: all var(--transition-base);
font-size: 0.8125rem;
line-height: 1.5;
color: var(--text-secondary);
}
.share-dialog .create-access-btn:hover {
background: linear-gradient(135deg, var(--accent-light) 0%, var(--accent) 100%);
transform: translateY(-1px);
/* Publish confirm button with icon */
.publish-confirm-btn {
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.publish-confirm-btn i {
font-size: 0.875rem;
}
/* ========================================
@ -1100,36 +1028,6 @@
text-align: left;
}
.password-form .form-field {
margin-bottom: 1rem;
}
.password-form .form-field label {
display: block;
font-family: 'Kingthings Typewriter', monospace;
font-size: 0.875rem;
color: var(--text-secondary);
margin-bottom: 0.5rem;
}
.password-form .form-field input {
width: 100%;
padding: 0.875rem 1rem;
font-family: 'Lora', Georgia, serif;
font-size: 1rem;
color: var(--text-primary);
background: white;
border: 2px solid var(--neutral-200);
border-radius: 0.375rem;
box-sizing: border-box;
transition: border-color var(--transition-base);
}
.password-form .form-field input:focus {
outline: none;
border-color: var(--accent);
}
.password-error {
display: flex;
align-items: center;
@ -1166,233 +1064,6 @@
transform: translateY(0);
}
/* ========================================
Publish Warning Dialog
A thoughtful caution before public publishing
======================================== */
.publish-warning-overlay {
position: fixed;
inset: 0;
background: rgba(41, 37, 36, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
animation: fadeIn 0.2s ease-out;
backdrop-filter: blur(3px);
}
.publish-warning-overlay.closing {
animation: fadeOut 0.2s ease-out forwards;
}
.publish-warning-dialog {
background: linear-gradient(135deg, rgba(255, 253, 250, 0.99) 0%, rgba(255, 251, 235, 1) 100%);
border: 2px solid var(--neutral-300);
border-radius: 0.75rem;
box-shadow:
var(--shadow-xl),
0 0 0 1px rgba(217, 119, 6, 0.1),
0 0 40px rgba(217, 119, 6, 0.08);
max-width: 480px;
width: 90%;
animation: warningSlideIn 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
overflow: hidden;
}
@keyframes warningSlideIn {
from {
opacity: 0;
transform: translateY(-20px) scale(0.95);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
/* Warning Header with Icon */
.publish-warning-header {
display: flex;
align-items: center;
gap: 0.875rem;
padding: 1.25rem 1.5rem;
background: linear-gradient(135deg, rgba(251, 191, 36, 0.08) 0%, rgba(245, 158, 11, 0.05) 100%);
border-bottom: 1px dashed rgba(217, 119, 6, 0.25);
position: relative;
}
.publish-warning-header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg,
transparent 0%,
rgba(245, 158, 11, 0.6) 20%,
rgba(217, 119, 6, 0.8) 50%,
rgba(245, 158, 11, 0.6) 80%,
transparent 100%
);
}
.warning-icon-wrapper {
display: flex;
align-items: center;
justify-content: center;
width: 2.5rem;
height: 2.5rem;
background: linear-gradient(135deg, rgba(251, 191, 36, 0.2) 0%, rgba(245, 158, 11, 0.15) 100%);
border: 1.5px solid rgba(217, 119, 6, 0.3);
border-radius: 50%;
flex-shrink: 0;
animation: warningPulse 2s ease-in-out infinite;
}
@keyframes warningPulse {
0%, 100% {
box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.3);
}
50% {
box-shadow: 0 0 0 6px rgba(245, 158, 11, 0);
}
}
.warning-icon-wrapper i {
font-size: 1.125rem;
color: #b45309;
}
.publish-warning-header h3 {
flex: 1;
font-family: 'Kingthings Typewriter', monospace;
font-size: 1.25rem;
color: var(--text-primary);
margin: 0;
letter-spacing: 0.02em;
}
.publish-warning-header .dialog-close-btn {
display: flex;
align-items: center;
justify-content: center;
width: 2rem;
height: 2rem;
background: transparent;
border: none;
border-radius: 50%;
color: var(--text-secondary);
cursor: pointer;
transition: all var(--transition-base);
}
.publish-warning-header .dialog-close-btn:hover {
background: rgba(217, 119, 6, 0.1);
color: #b45309;
}
/* Warning Body */
.publish-warning-body {
padding: 1.5rem;
}
.warning-text {
font-family: 'Lora', Georgia, serif;
font-size: 0.9375rem;
line-height: 1.7;
color: var(--text-primary);
margin: 0 0 1.25rem 0;
text-align: justify;
hyphens: auto;
}
/* Recommendation Callout */
.warning-recommendation {
display: flex;
align-items: flex-start;
gap: 0.75rem;
padding: 1rem 1.125rem;
background: linear-gradient(135deg, rgba(16, 185, 129, 0.06) 0%, rgba(5, 150, 105, 0.04) 100%);
border: 1.5px solid rgba(16, 185, 129, 0.2);
border-radius: 0.5rem;
border-left: 3px solid var(--success);
}
.warning-recommendation i {
font-size: 1rem;
color: var(--success);
margin-top: 0.125rem;
flex-shrink: 0;
}
.warning-recommendation span {
font-family: 'Kingthings Typewriter', monospace;
font-size: 0.8125rem;
line-height: 1.5;
color: var(--text-secondary);
}
/* Warning Actions */
.publish-warning-actions {
display: flex;
gap: 0.75rem;
justify-content: flex-end;
padding: 1rem 1.5rem;
background: rgba(255, 255, 255, 0.6);
border-top: 1px dashed var(--neutral-200);
}
.publish-warning-actions .cancel-btn {
padding: 0.625rem 1.25rem;
background: transparent;
color: var(--text-secondary);
border: 2px solid var(--neutral-300);
border-radius: 0.375rem;
font-family: 'Kingthings Typewriter', monospace;
font-size: 0.9375rem;
cursor: pointer;
transition: all var(--transition-base);
}
.publish-warning-actions .cancel-btn:hover {
background: var(--neutral-100);
border-color: var(--neutral-400);
color: var(--text-primary);
}
.publish-confirm-btn {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.625rem 1.25rem;
background: linear-gradient(135deg, #d97706 0%, #b45309 100%);
color: white;
border: 2px solid #92400e;
border-radius: 0.375rem;
font-family: 'Kingthings Typewriter', monospace;
font-size: 0.9375rem;
font-weight: 500;
cursor: pointer;
transition: all var(--transition-base);
}
.publish-confirm-btn:hover {
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(217, 119, 6, 0.25);
}
.publish-confirm-btn:active {
transform: translateY(0);
}
.publish-confirm-btn i {
font-size: 0.875rem;
}
/* ========================================
Story Stats (Public Page)
Word count and reading time display
@ -1452,50 +1123,6 @@
border-top: 1px dashed var(--neutral-300);
}
.pagination-button {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1.25rem;
background: linear-gradient(135deg, var(--neutral-100) 0%, var(--neutral-200) 100%);
color: var(--text-primary);
border: 2px solid var(--neutral-300);
border-radius: 0.375rem;
font-family: 'Kingthings Typewriter', monospace;
font-size: 0.9375rem;
font-weight: 500;
cursor: pointer;
transition: all var(--transition-base);
text-decoration: none;
}
.pagination-button:hover:not(.disabled) {
background: linear-gradient(135deg, var(--accent) 0%, var(--accent-dark) 100%);
color: white;
border-color: var(--accent-dark);
transform: translateY(-1px);
}
.pagination-button:active:not(.disabled) {
transform: translateY(0);
}
.pagination-button.disabled {
opacity: 0.4;
cursor: not-allowed;
}
.pagination-button i {
font-size: 0.75rem;
}
.pagination-info {
font-family: 'Kingthings Typewriter', monospace;
font-size: 0.875rem;
color: var(--text-secondary);
padding: 0 1rem;
}
/* Responsive pagination */
@media only screen and (max-width: 480px) {
.story-pagination {
@ -1503,12 +1130,12 @@
gap: 0.75rem;
}
.pagination-button {
.story-pagination .pagination-button {
padding: 0.625rem 1rem;
font-size: 0.875rem;
}
.pagination-info {
.story-pagination .pagination-info {
width: 100%;
text-align: center;
order: -1;

View file

@ -1,24 +1,22 @@
{{> header}}
<main class="admin-page">
<main class="page">
<div class="admin-layout">
{{> partials/admin-nav}}
<div class="admin-content">
<!-- Admin Header -->
<div class="admin-header">
<div class="admin-header-content">
<h1>{{msg.admin_dashboard_title}}</h1>
<p class="admin-subtitle">{{msg.admin_dashboard_subtitle}}</p>
<div class="page-header">
<div class="page-header__content">
<h1 class="page-header__title">{{msg.admin_dashboard_title}}</h1>
<p class="page-header__subtitle">{{msg.admin_dashboard_subtitle}}</p>
</div>
</div>
<!-- Server Settings Section -->
<section class="admin-section">
<section class="paper-section">
<div class="section-header">
<div class="section-title-group">
<h2>{{msg.admin_serversettings_title}}</h2>
</div>
<h2 class="section-header__title">{{msg.admin_serversettings_title}}</h2>
</div>
<p class="section-description">{{msg.admin_serversettings_description}}</p>
@ -26,17 +24,19 @@
hx-post="/admin/settings"
hx-swap="none">
<div class="form-group">
<label for="contact">{{msg.admin_serversettings_contact_label}}</label>
<label for="contact"
class="form-field__label">{{msg.admin_serversettings_contact_label}}</label>
<div class="form-input-group">
<input id="contact" name="contact" type="email" value="{{contactEmail}}"
<input id="contact" name="contact" type="email" value="{{contactEmail}}" class="form-input"
placeholder="{{msg.admin_serversettings_contact_placeholder}}"/>
</div>
</div>
<div class="form-group">
<label for="defaultLocale">{{msg.admin_serversettings_default_locale_label}}</label>
<label for="defaultLocale"
class="form-field__label">{{msg.admin_serversettings_default_locale_label}}</label>
<div class="form-input-group">
<select id="defaultLocale" name="defaultLocale" class="settings-select">
<select id="defaultLocale" name="defaultLocale" class="form-select">
{{#availableLocales}}
<option value="{{tag}}" {{#selected}}selected{{/selected}}>{{label}}</option>
{{/availableLocales}}
@ -45,10 +45,11 @@
</div>
<div class="form-group">
<label for="message">{{msg.admin_serversettings_message_label}}</label>
<label for="message"
class="form-field__label">{{msg.admin_serversettings_message_label}}</label>
<div class="form-input-group">
<textarea id="message" name="message" rows="4"
class="settings-textarea">{{serverMessage}}</textarea>
class="form-textarea">{{serverMessage}}</textarea>
</div>
</div>

View file

@ -1,24 +1,22 @@
{{> header}}
<main class="admin-page">
<main class="page">
<div class="admin-layout">
{{> partials/admin-nav}}
<div class="admin-content">
<!-- Admin Header -->
<div class="admin-header">
<div class="admin-header-content">
<h1>{{msg.admin_users_title}}</h1>
<p class="admin-subtitle">{{msg.admin_users_subtitle}}</p>
<div class="page-header">
<div class="page-header__content">
<h1 class="page-header__title">{{msg.admin_users_title}}</h1>
<p class="page-header__subtitle">{{msg.admin_users_subtitle}}</p>
</div>
</div>
<!-- User Management Section -->
<section class="admin-section">
<section class="paper-section">
<div class="section-header">
<div class="section-title-group">
<h2>{{msg.admin_users_list_title}}</h2>
</div>
<h2 class="section-header__title">{{msg.admin_users_list_title}}</h2>
</div>
<p class="section-description">{{msg.admin_users_description}}</p>

View file

@ -1,15 +1,15 @@
{{> header}}
<main class="admin-page">
<main class="page">
<div class="admin-layout">
{{> partials/admin-nav}}
<div class="admin-content">
<!-- Admin Header -->
<div class="admin-header">
<div class="admin-header-content">
<h1>{{msg.admin_whitelist_management_title}}</h1>
<p class="admin-subtitle">
<div class="page-header">
<div class="page-header__content">
<h1 class="page-header__title">{{msg.admin_whitelist_management_title}}</h1>
<p class="page-header__subtitle">
{{#whitelist.enabled}}
{{msg.admin_whitelist_enabled_subtitle}}
{{/whitelist.enabled}}
@ -18,7 +18,7 @@
{{/whitelist.enabled}}
</p>
</div>
<div class="admin-header-actions">
<div class="page-header__actions">
<form action="/admin/whitelist/toggle" method="post" class="logout-form"
hx-post="/admin/whitelist/toggle"
hx-swap="none">
@ -34,11 +34,9 @@
</div>
<!-- Whitelist Management Section -->
<section class="admin-section">
<section class="paper-section">
<div class="section-header">
<div class="section-title-group">
<h2>{{msg.admin_whitelist_add_title}}</h2>
</div>
<h2 class="section-header__title">{{msg.admin_whitelist_add_title}}</h2>
</div>
<p class="section-description">{{msg.admin_whitelist_description}}</p>
@ -48,9 +46,9 @@
hx-swap="innerHTML"
hx-on::after-request="this.reset()">
<div class="form-group">
<label for="email">{{msg.admin_whitelist_email_label}}</label>
<label for="email" class="form-field__label">{{msg.admin_whitelist_email_label}}</label>
<div class="form-input-group">
<input id="email" name="email" type="email"
<input id="email" name="email" type="email" class="form-input"
placeholder="{{msg.admin_whitelist_email_placeholder}}" required/>
<button type="submit" class="add-button">{{msg.admin_whitelist_add_button}}</button>
</div>

View file

@ -1,7 +1,7 @@
{{> header}}
<main class="author-page">
<div class="author-container">
<main class="page">
<div class="page__container page__container--author">
<!-- Stories Section -->
<section class="stories-section">
<!-- Author Info inside the card -->
@ -16,14 +16,14 @@
</header>
{{#hasStories}}
<div class="stories-list">
<div class="card-list">
{{#stories}}
<a href="/a/{{urlPenName}}/{{urlName}}" class="story-card">
<div class="story-info">
<h2 class="story-name">{{name}}</h2>
<span class="story-date">{{msg.author_story_published_on}} {{publishedAt}}</span>
<a href="/a/{{urlPenName}}/{{urlName}}" class="card card--gradient card--interactive">
<div class="card__info">
<h2 class="card__title">{{name}}</h2>
<span class="card__subtitle">{{msg.author_story_published_on}} {{publishedAt}}</span>
</div>
<i class="fa-solid fa-chevron-right story-arrow"></i>
<i class="fa-solid fa-chevron-right card__arrow"></i>
</a>
{{/stories}}
</div>

View file

@ -1,14 +1,14 @@
{{> header}}
<main class="dashboard-page">
<div class="dashboard-container">
<main class="page">
<div class="page__container">
<!-- Dashboard Header -->
<div class="dashboard-header">
<div class="dashboard-header-content">
<h1>{{msg.dashboard_title}}</h1>
<p class="dashboard-subtitle">{{msg.dashboard_subtitle}} {{username}}</p>
<div class="page-header">
<div class="page-header__content">
<h1 class="page-header__title">{{msg.dashboard_title}}</h1>
<p class="page-header__subtitle">{{msg.dashboard_subtitle}} {{username}}</p>
</div>
<div class="dashboard-header-actions">
<div class="page-header__actions">
{{#isAdmin}}
<a href="/admin" class="manage-server-button">{{msg.dashboard_manage_server}}</a>
{{/isAdmin}}
@ -19,11 +19,9 @@
</div>
<!-- Projects Section -->
<section class="dashboard-section">
<section class="paper-section">
<div class="section-header">
<div class="section-title-group">
<h2>{{msg.dashboard_projects_title}}</h2>
</div>
<h2 class="section-header__title">{{msg.dashboard_projects_title}}</h2>
</div>
<p class="section-description">{{msg.dashboard_projects_description}}</p>
@ -35,11 +33,9 @@
</section>
<!-- Account Section -->
<section class="dashboard-section">
<section class="paper-section">
<div class="section-header">
<div class="section-title-group">
<h2>{{msg.dashboard_account_title}}</h2>
</div>
<h2 class="section-header__title">{{msg.dashboard_account_title}}</h2>
</div>
<div class="account-container">
<div class="account-info">

View file

@ -1,8 +1,8 @@
{{> header}}
<main class="login-page">
<div class="login-container">
<div class="login-card">
<main class="page page--centered">
<div class="page__container page__container--narrow">
<div class="paper-section paper-section--compact">
<div class="login-header">
<h1>{{msg.login_title}}</h1>
<p>{{msg.login_subtitle}}</p>
@ -10,14 +10,14 @@
<form action="/login" method="post" class="login-form">
<div class="form-field">
<label for="email">{{msg.login_email_label}}</label>
<input type="email" id="email" name="email" required autofocus
<label for="email" class="form-field__label">{{msg.login_email_label}}</label>
<input type="email" id="email" name="email" class="form-input" required autofocus
placeholder="{{msg.login_email_placeholder}}">
</div>
<div class="form-field">
<label for="password">{{msg.login_password_label}}</label>
<input type="password" id="password" name="password" required
<label for="password" class="form-field__label">{{msg.login_password_label}}</label>
<input type="password" id="password" name="password" class="form-input" required
placeholder="{{msg.login_password_placeholder}}">
</div>

View file

@ -1,7 +1,7 @@
{{> header}}
<main class="error-page">
<div class="error-container">
<main class="page page--centered">
<div class="page__container page__container--error">
<div class="error-card notfound">
<div class="error-icon">📄</div>
<h1 class="error-title">{{msg.error_notfound_title}}</h1>

View file

@ -1,21 +1,21 @@
{{#projects}}
{{#hasProjects}}
<div class="projects-list">
<div class="card-list">
{{#items}}
<a href="/story/{{uuid}}" class="project-card">
<div class="project-info">
<span class="project-name">{{name}}</span>
<span class="project-sync-date">
<a href="/story/{{uuid}}" class="card card--interactive">
<div class="card__info">
<span class="card__title">{{name}}</span>
<span class="card__subtitle">
{{msg.dashboard_last_sync}}: {{lastSync}}
</span>
</div>
<i class="fa-solid fa-chevron-right project-arrow"></i>
<i class="fa-solid fa-chevron-right card__arrow"></i>
</a>
{{/items}}
</div>
{{/hasProjects}}
{{^hasProjects}}
<div class="projects-empty">
<div class="empty-state">
<p>{{msg.dashboard_no_projects}}</p>
</div>
{{/hasProjects}}

View file

@ -1,26 +1,26 @@
<div class="publish-warning-overlay" id="publish-warning-overlay" onclick="closePublishWarning(event)">
<div class="publish-warning-dialog" onclick="event.stopPropagation()">
<div class="publish-warning-header">
<div class="dialog-overlay" id="publish-warning-overlay" onclick="closePublishWarning(event)">
<div class="dialog dialog--warning" onclick="event.stopPropagation()">
<div class="dialog__header dialog__header--warning">
<div class="warning-icon-wrapper">
<i class="fas fa-exclamation-triangle"></i>
</div>
<h3>{{msg.story_publish_warning_title}}</h3>
<button type="button" class="dialog-close-btn" onclick="closePublishWarning()">
<button type="button" class="dialog__close" onclick="closePublishWarning()">
<i class="fas fa-times"></i>
</button>
</div>
<div class="publish-warning-body">
<div class="dialog__body">
<p class="warning-text">{{msg.story_publish_warning_content}}</p>
<div class="warning-recommendation">
<i class="fas fa-lightbulb"></i>
<span>{{msg.story_publish_warning_recommendation}}</span>
</div>
</div>
<div class="publish-warning-actions">
<button type="button" class="cancel-btn" onclick="closePublishWarning()">
<div class="dialog__actions">
<button type="button" class="btn btn--ghost" onclick="closePublishWarning()">
{{msg.story_cancel}}
</button>
<button type="button" class="publish-confirm-btn" onclick="confirmPublish()">
<button type="button" class="btn btn--accent publish-confirm-btn" onclick="confirmPublish()">
<i class="fas fa-globe"></i> {{msg.story_publish_publicly}}
</button>
</div>

View file

@ -1,8 +1,8 @@
<div class="share-dialog-overlay" id="share-dialog-overlay" onclick="closeShareDialog(event)">
<div class="share-dialog" onclick="event.stopPropagation()">
<div class="share-dialog-header">
<div class="dialog-overlay" id="share-dialog-overlay" onclick="closeShareDialog(event)">
<div class="dialog" onclick="event.stopPropagation()">
<div class="dialog__header">
<h3>{{msg.story_share_private_title}}</h3>
<button type="button" class="dialog-close-btn" onclick="closeShareDialog()">
<button type="button" class="dialog__close" onclick="closeShareDialog()">
<i class="fas fa-times"></i>
</button>
</div>
@ -10,33 +10,35 @@
hx-target="#publish-section"
hx-swap="outerHTML"
hx-on::after-request="closeShareDialog()">
<div class="share-dialog-body">
<div class="dialog__body">
<div class="form-field">
<label for="access-password">{{msg.story_access_password}}</label>
<label for="access-password" class="form-field__label">{{msg.story_access_password}}</label>
<input type="text"
id="access-password"
name="password"
class="form-input"
required
minlength="1"
autocomplete="off"
placeholder="{{msg.story_access_password_placeholder}}">
</div>
<div class="form-field">
<label for="access-expires">
<label for="access-expires" class="form-field__label">
{{msg.story_access_expires}}
<span class="optional-label">({{msg.story_access_optional}})</span>
</label>
<input type="date"
id="access-expires"
name="expiresAt"
class="form-input"
min="{{minDate}}">
</div>
</div>
<div class="share-dialog-actions">
<button type="button" class="cancel-btn" onclick="closeShareDialog()">
<div class="dialog__actions">
<button type="button" class="btn btn--ghost" onclick="closeShareDialog()">
{{msg.story_cancel}}
</button>
<button type="submit" class="create-access-btn">
<button type="submit" class="btn btn--accent">
{{msg.story_create_access}}
</button>
</div>

View file

@ -26,7 +26,7 @@
{{/.}}
{{/items}}
{{^items}}
<div class="users-empty">
<div class="empty-state">
<p>{{msg.admin_users_empty}}</p>
</div>
{{/items}}

View file

@ -18,7 +18,7 @@
{{/.}}
{{/items}}
{{^items}}
<div class="whitelist-empty">
<div class="empty-state">
<p>{{msg.admin_whitelist_empty}}</p>
</div>
{{/items}}

View file

@ -1,7 +1,7 @@
{{> header}}
<main class="error-page">
<div class="error-container">
<main class="page page--centered">
<div class="page__container page__container--error">
<div class="error-card servererror">
<div class="error-icon">⚙️</div>
<h1 class="error-title">{{msg.error_servererror_title}}</h1>

View file

@ -1,7 +1,7 @@
{{> header}}
<main class="error-page">
<div class="error-container">
<main class="page page--centered">
<div class="page__container page__container--error">
<div class="error-card">
<div class="error-icon">🔒</div>
<h1 class="error-title">{{msg.error_unauthorized_title}}</h1>