9a848e8c53
Implement a command palette accessible via CMD+K/Ctrl+K using the ninja-keys web component. Features include: - New /api/cmd-k endpoint serving dynamic CV entries (experiences, projects, courses) - Language-aware responses with 1-hour cache headers - Scroll-to-section functionality for quick navigation - Enhanced keyboard shortcuts modal with CMD+K documentation - Comprehensive test coverage for API and UI interactions Also includes cleanup of deprecated debug test files and various UI polish improvements to contact form, themes, and action bar components.
657 lines
14 KiB
CSS
657 lines
14 KiB
CSS
/* Single Black Top Bar */
|
|
.action-bar {
|
|
background: var(--action-bar-bg, #2b2b2b);
|
|
color: var(--action-bar-text, #ffffff);
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 100;
|
|
box-shadow: var(--shadow-md, 0 2px 8px rgba(0,0,0,0.15));
|
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
|
overflow: visible; /* Allow tooltips to extend beyond action bar */
|
|
}
|
|
|
|
.action-bar-content {
|
|
max-width: 100%;
|
|
margin: 0 auto;
|
|
padding: 0;
|
|
display: grid;
|
|
grid-template-columns: 1fr auto 1fr;
|
|
align-items: stretch;
|
|
gap: 2rem;
|
|
height: 50px;
|
|
overflow: visible; /* Allow tooltips to extend beyond content area */
|
|
}
|
|
|
|
/* Left: Site Title */
|
|
.site-title {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
justify-self: start;
|
|
white-space: nowrap;
|
|
padding: 0;
|
|
height: 100%;
|
|
}
|
|
|
|
.site-title-left {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.site-icon {
|
|
color: #fff;
|
|
flex-shrink: 0;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
height: 36px;
|
|
padding: 0 .5rem 0 1.5rem;
|
|
}
|
|
|
|
/* Mobile icon hidden by default, shown only on mobile */
|
|
.site-icon-mobile {
|
|
display: none;
|
|
color: #fff;
|
|
flex-shrink: 0;
|
|
margin-right: 0.5rem;
|
|
}
|
|
|
|
/* Site logo and title links */
|
|
.site-logo-link,
|
|
.site-title-link {
|
|
text-decoration: none;
|
|
color: inherit;
|
|
display: flex;
|
|
align-items: center;
|
|
height: 36px;
|
|
transition: opacity 0.2s ease;
|
|
}
|
|
|
|
.site-logo-link:hover,
|
|
.site-title-link:hover {
|
|
opacity: 0.8;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.site-logo-link {
|
|
padding: 0;
|
|
}
|
|
|
|
/* Ensure Iconify icons display properly */
|
|
.iconify,
|
|
iconify-icon {
|
|
display: inline-block;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.site-title-text {
|
|
font-size: 1.05rem;
|
|
font-weight: 500;
|
|
color: #fff;
|
|
letter-spacing: -0.01em;
|
|
line-height: 1;
|
|
display: flex;
|
|
align-items: center;
|
|
height: 36px;
|
|
padding: 0 1rem 0 0rem;
|
|
}
|
|
|
|
/* Center: View controls with labels */
|
|
.view-controls-center {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
gap: 2.5rem;
|
|
justify-self: center;
|
|
flex-shrink: 0;
|
|
white-space: nowrap;
|
|
height: 100%;
|
|
}
|
|
|
|
.selector-group {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.selector-label {
|
|
font-size: 0.875rem;
|
|
color: rgba(255,255,255,0.85);
|
|
font-weight: 500;
|
|
white-space: nowrap;
|
|
letter-spacing: -0.01em;
|
|
line-height: 1;
|
|
display: flex;
|
|
align-items: center;
|
|
height: 36px;
|
|
}
|
|
|
|
.selector-label span {
|
|
color: #27ae60;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.language-toggle,
|
|
.cv-length-toggle,
|
|
.logo-toggle {
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
/* Right: Action buttons */
|
|
.action-buttons {
|
|
justify-self: end;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.htmx-indicator {
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.lang-btn {
|
|
padding: 0.4rem 1rem;
|
|
border: 1px solid rgba(255,255,255,0.3);
|
|
background: transparent;
|
|
color: white;
|
|
border-radius: 3px;
|
|
cursor: pointer;
|
|
font-size: 1rem;
|
|
font-weight: 400;
|
|
text-transform: capitalize;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.lang-btn:hover {
|
|
background: rgba(255,255,255,0.1);
|
|
border-color: rgba(255,255,255,0.5);
|
|
}
|
|
|
|
.lang-btn.active {
|
|
background: #27ae60 !important;
|
|
border-color: #27ae60 !important;
|
|
font-weight: 500;
|
|
}
|
|
|
|
/* Icon Toggle Switches */
|
|
.icon-toggle {
|
|
position: relative;
|
|
display: flex;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.icon-toggle input[type="checkbox"] {
|
|
position: absolute;
|
|
opacity: 0;
|
|
width: 0;
|
|
height: 0;
|
|
}
|
|
|
|
.icon-toggle-slider {
|
|
position: relative;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
width: 75px;
|
|
height: 30px;
|
|
background: #e0e0e0;
|
|
border: 2px solid #d0d0d0;
|
|
border-radius: 15px;
|
|
padding: 0 6px;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.icon-toggle-slider::before {
|
|
content: '';
|
|
position: absolute;
|
|
width: 24px;
|
|
height: 24px;
|
|
left: 2px;
|
|
background: white;
|
|
border-radius: 50%;
|
|
transition: transform 0.3s ease;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
|
|
z-index: 2;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.icon-toggle input:checked + .icon-toggle-slider::before {
|
|
transform: translateX(43px);
|
|
}
|
|
|
|
.icon-toggle input:checked + .icon-toggle-slider {
|
|
background: #27ae60;
|
|
border-color: #229954;
|
|
}
|
|
|
|
.icon-toggle-slider .icon-left,
|
|
.icon-toggle-slider .icon-right {
|
|
position: absolute;
|
|
z-index: 3;
|
|
transition: all 0.3s ease;
|
|
flex-shrink: 0;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.icon-toggle-slider .icon-left {
|
|
left: 6px;
|
|
}
|
|
|
|
.icon-toggle-slider .icon-right {
|
|
right: 6px;
|
|
}
|
|
|
|
.icon-toggle input:not(:checked) + .icon-toggle-slider .icon-left {
|
|
color: #333 !important;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.icon-toggle input:not(:checked) + .icon-toggle-slider .icon-right {
|
|
color: #999 !important;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.icon-toggle input:checked + .icon-toggle-slider .icon-left {
|
|
color: rgba(255,255,255,0.4) !important;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.icon-toggle input:checked + .icon-toggle-slider .icon-right {
|
|
color: white !important;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.icon-toggle input:focus + .icon-toggle-slider {
|
|
box-shadow: 0 0 0 3px rgba(39, 174, 96, 0.2);
|
|
}
|
|
|
|
/* Language selector wrapper - contains indicators outside swap target */
|
|
.language-selector-wrapper {
|
|
position: relative;
|
|
display: inline-flex;
|
|
height: 100%;
|
|
/* Ensure wrapper doesn't create extra spacing */
|
|
width: fit-content;
|
|
}
|
|
|
|
/* Language selector - matching action button style */
|
|
.language-selector {
|
|
display: inline-flex;
|
|
gap: 0;
|
|
padding: 0;
|
|
padding-left: 1rem; /* Space after the title */
|
|
margin-right: 0;
|
|
background: transparent;
|
|
border-radius: 0;
|
|
height: 100%;
|
|
align-items: stretch;
|
|
}
|
|
|
|
/* Position language indicators next to their respective buttons */
|
|
#lang-indicator-en,
|
|
#lang-indicator-es {
|
|
position: absolute;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
pointer-events: none;
|
|
z-index: 10;
|
|
}
|
|
|
|
/* Position indicators inside the button visual area */
|
|
#lang-indicator-en {
|
|
left: calc(1rem + 50px); /* Inside first button */
|
|
}
|
|
|
|
#lang-indicator-es {
|
|
left: calc(1rem + 135px); /* Inside second button */
|
|
}
|
|
|
|
.selector-btn {
|
|
padding: 0 1.5rem;
|
|
background: transparent;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 0;
|
|
cursor: pointer;
|
|
font-size: 1rem;
|
|
font-weight: 500;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
/* gap: 0.5rem; */
|
|
gap: 0rem;
|
|
text-decoration: none;
|
|
white-space: nowrap;
|
|
letter-spacing: -0.01em;
|
|
height: 100%;
|
|
line-height: 1;
|
|
transition: all 0.2s ease;
|
|
outline: none !important;
|
|
box-shadow: none !important;
|
|
min-width: 50px!important;
|
|
}
|
|
|
|
.selector-btn:focus,
|
|
.selector-btn:focus-visible,
|
|
.selector-btn:active {
|
|
outline: none !important;
|
|
box-shadow: none !important;
|
|
}
|
|
|
|
.selector-btn:hover {
|
|
background: #666;
|
|
}
|
|
|
|
.selector-btn:hover iconify-icon {
|
|
color: #27ae60;
|
|
}
|
|
|
|
.selector-btn.active {
|
|
background: #27ae60;
|
|
color: white;
|
|
}
|
|
|
|
.selector-btn:not(.active) {
|
|
background: transparent;
|
|
color: white;
|
|
}
|
|
|
|
/* Language selector buttons - no global animations (applied in responsive range only) */
|
|
|
|
/* Action buttons - transparent with white text */
|
|
.action-btn {
|
|
padding: 0 1.5rem;
|
|
background: transparent;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 0;
|
|
cursor: pointer;
|
|
font-size: 1rem;
|
|
font-weight: 500;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 0.5rem;
|
|
text-decoration: none;
|
|
white-space: nowrap;
|
|
letter-spacing: -0.01em;
|
|
height: 100%;
|
|
line-height: 1;
|
|
transition: background-color 0.3s ease, color 0.3s ease; /* Smooth color transitions */
|
|
}
|
|
|
|
.action-btn iconify-icon {
|
|
color: white;
|
|
transition: color 0.3s ease; /* Smooth icon color transition */
|
|
}
|
|
|
|
.action-btn:hover {
|
|
background: #ddd;
|
|
color: #333;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.action-btn:hover iconify-icon {
|
|
color: #27ae60;
|
|
}
|
|
|
|
/* PDF Download button - gray by default, red on hover */
|
|
.pdf-btn {
|
|
background: transparent !important; /* Transparent like other buttons */
|
|
color: white !important;
|
|
}
|
|
|
|
.pdf-btn:hover,
|
|
.pdf-btn.pdf-hover-sync {
|
|
background: #cd6060 !important; /* PDF red on hover */
|
|
color: white !important;
|
|
}
|
|
|
|
.pdf-btn iconify-icon {
|
|
color: white !important;
|
|
filter: brightness(0) invert(1); /* Always white */
|
|
transition: filter 0.3s ease;
|
|
}
|
|
|
|
.pdf-btn:hover iconify-icon {
|
|
color: white !important;
|
|
filter: brightness(0) invert(1); /* Keep white on hover */
|
|
}
|
|
|
|
/* Print Friendly button - white bg with green icon on hover */
|
|
.print-btn {
|
|
background: transparent !important;
|
|
color: white !important;
|
|
}
|
|
|
|
.print-btn:hover,
|
|
.print-btn.print-hover-sync {
|
|
background: white !important; /* White background on hover */
|
|
color: #27ae60 !important; /* Green icon on hover */
|
|
}
|
|
|
|
.print-btn iconify-icon {
|
|
color: white; /* White icon by default */
|
|
}
|
|
|
|
.print-btn:hover iconify-icon,
|
|
.print-btn.print-hover-sync iconify-icon {
|
|
color: #27ae60; /* Green icon on hover */
|
|
}
|
|
|
|
/* CV Length Toggle - Center of action bar */
|
|
.cv-length-toggle {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
justify-self: center;
|
|
}
|
|
|
|
.length-btn {
|
|
padding: 0.4rem 1rem;
|
|
border: 1px solid rgba(255,255,255,0.4);
|
|
background: rgba(255,255,255,0.1);
|
|
color: white;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 0.9rem;
|
|
font-weight: 500;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.length-btn:hover {
|
|
background: rgba(255,255,255,0.2);
|
|
border-color: rgba(255,255,255,0.6);
|
|
}
|
|
|
|
.length-btn.active {
|
|
background: white;
|
|
color: #1a1a1a;
|
|
border-color: white;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* Action buttons styling (already positioned by grid) */
|
|
.action-buttons,
|
|
.action-buttons-right {
|
|
display: flex;
|
|
gap: 0;
|
|
align-items: stretch;
|
|
height: 100%;
|
|
overflow: visible; /* Allow tooltips to extend beyond button container */
|
|
flex-wrap: nowrap; /* Keep buttons in single row */
|
|
}
|
|
|
|
.action-buttons-right {
|
|
justify-self: end;
|
|
margin-left: auto;
|
|
}
|
|
|
|
/* ========================================
|
|
RESPONSIVE ACTION BUTTONS - Scale to fit
|
|
Prevents button overflow on narrow screens
|
|
======================================== */
|
|
|
|
/* Intermediate screens: shrink buttons to fit */
|
|
@media (min-width: 901px) and (max-width: 1400px) {
|
|
.action-buttons-right {
|
|
flex-shrink: 1;
|
|
min-width: 0; /* Allow shrinking */
|
|
}
|
|
|
|
.action-buttons-right .action-btn {
|
|
flex-shrink: 1;
|
|
min-width: 40px; /* Minimum touchable size */
|
|
padding: 0 0.5rem; /* Reduce padding as needed */
|
|
}
|
|
}
|
|
|
|
/* Narrow desktop: icon-only buttons that scale */
|
|
@media (min-width: 541px) and (max-width: 900px) {
|
|
.action-buttons-right {
|
|
display: flex !important; /* Show on tablet */
|
|
flex-shrink: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.action-buttons-right .action-btn {
|
|
width: auto;
|
|
min-width: 36px;
|
|
padding: 0 0.4rem;
|
|
font-size: 0; /* Icon only */
|
|
flex-shrink: 1;
|
|
}
|
|
|
|
.action-buttons-right .action-btn iconify-icon {
|
|
width: 20px;
|
|
height: 20px;
|
|
}
|
|
}
|
|
|
|
/* Very narrow: use CSS clamp for fluid button sizing */
|
|
@media (min-width: 901px) and (max-width: 1100px) {
|
|
.action-btn {
|
|
/* Fluid width that scales with viewport */
|
|
width: clamp(35px, 4vw, 50px) !important;
|
|
padding: 0 clamp(0.3rem, 0.8vw, 1rem) !important;
|
|
}
|
|
}
|
|
|
|
/* ============================================================================
|
|
HTMX Loading Indicators
|
|
========================================================================= */
|
|
|
|
/* Base indicator styles - hidden by default with opacity for smooth transitions */
|
|
.htmx-indicator {
|
|
opacity: 0; /* Hidden by default */
|
|
transition: opacity 200ms ease-in-out;
|
|
pointer-events: none;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
position: absolute; /* Remove from layout flow to prevent spacing issues */
|
|
}
|
|
|
|
/* Override for when request is active - must come AFTER base rule */
|
|
.htmx-indicator.htmx-request,
|
|
#lang-indicator-en.htmx-request,
|
|
#lang-indicator-es.htmx-request {
|
|
opacity: 1 !important; /* Force visible state */
|
|
}
|
|
|
|
/* Ensure iconify-icon indicators override global iconify-icon display style */
|
|
iconify-icon.htmx-indicator {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
/* Show indicators during HTMX requests */
|
|
/* Using span wrapper, so target span.htmx-request specifically */
|
|
span.htmx-request.htmx-indicator,
|
|
.htmx-request .htmx-indicator,
|
|
.htmx-request.htmx-indicator {
|
|
opacity: 1 !important;
|
|
}
|
|
|
|
/* Spinning animation for loading icons */
|
|
.htmx-indicator.spinning {
|
|
animation: htmx-spin 1s linear infinite;
|
|
}
|
|
|
|
@keyframes htmx-spin {
|
|
from { transform: rotate(0deg); }
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
|
|
/* Indicator size variants */
|
|
.htmx-indicator.small {
|
|
width: 14px;
|
|
height: 14px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.htmx-indicator.medium {
|
|
width: 18px;
|
|
height: 18px;
|
|
font-size: 18px;
|
|
}
|
|
|
|
.htmx-indicator.large {
|
|
width: 24px;
|
|
height: 24px;
|
|
font-size: 24px;
|
|
}
|
|
|
|
/* Positioning variants */
|
|
.htmx-indicator.inline {
|
|
display: inline-flex;
|
|
margin-left: 8px;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.htmx-indicator.inline-start {
|
|
display: inline-flex;
|
|
margin-right: 8px;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
/* Color variants for different contexts */
|
|
.htmx-indicator.light {
|
|
color: rgba(255, 255, 255, 0.9);
|
|
}
|
|
|
|
.htmx-indicator.dark {
|
|
color: rgba(0, 0, 0, 0.7);
|
|
}
|
|
|
|
.htmx-indicator.accent {
|
|
color: #27ae60;
|
|
}
|
|
|
|
/* Respect reduced motion preference */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.htmx-indicator.spinning {
|
|
animation: none;
|
|
}
|
|
.htmx-indicator {
|
|
transition: none;
|
|
}
|
|
}
|
|
|
|
/* Legacy loader class for backward compatibility */
|
|
.loader {
|
|
border: 2px solid #f3f3f3;
|
|
border-top: 2px solid white;
|
|
border-radius: 50%;
|
|
width: 20px;
|
|
height: 20px;
|
|
animation: htmx-spin 1s linear infinite;
|
|
}
|
|
|
|
/* ============================================================================
|
|
Inline Loading States for HTMX Transitions
|
|
========================================================================= */
|
|
|
|
/* Inline loading states - no blocking overlay, smooth transitions only */
|
|
/* Language selector buttons already have htmx-indicator spinners */
|
|
/* CV content areas show subtle fade during swap */
|