portfolio-page/src/scss/globals.scss

232 lines
5.0 KiB
SCSS

@use 'variables' as *;
@use 'mixins' as *;
@forward 'variables';
@forward 'mixins';
// Global CSS custom properties for accessibility
:root {
--color-primary: #042c53;
--color-secondary: #0c447c;
--color-accent-text: #412402;
--color-accent-deco: #ef9f27;
--color-text-on-dark: #e6f1fb;
--color-focus-ring: #042c53;
--color-text-aaa-light: #1a1a1a;
--color-text-aaa-dark: #e6f1fb;
--box-shadow-hover: rgba(4, 44, 83, 0.15);
--font-headline: 'Comfortaa', sans-serif;
--font-body: 'Quicksand', sans-serif;
--contact-button-bg: #042c53;
--contact-button-text: #e6f1fb;
--contact-button-hover-bg: #0c447c;
}
// Dark mode is controlled via [data-theme='dark'] attribute on <html>.
// OS-level prefers-color-scheme is intentionally NOT used here to avoid
// --color-primary flipping to #E6F1FB, which breaks the always-dark navbar.
// Screen Reader Only Classes - Central Definition
.sr-only {
position: absolute !important;
width: 1px !important;
height: 1px !important;
padding: 0 !important;
margin: -1px !important;
overflow: hidden !important;
clip: rect(0, 0, 0, 0) !important;
white-space: nowrap !important;
border: 0 !important;
}
.sr-only-focusable:focus {
position: static !important;
width: auto !important;
height: auto !important;
padding: 0.5rem !important;
margin: 0 !important;
overflow: visible !important;
clip: auto !important;
white-space: normal !important;
background-color: var(--color-primary) !important;
color: var(--color-text-aaa-dark) !important;
border: 2px solid var(--color-focus-ring) !important;
border-radius: 4px !important;
text-decoration: none !important;
z-index: 1000 !important;
outline: 2px solid var(--color-focus-ring) !important;
outline-offset: 2px !important;
}
// Skip link for accessibility
.skip-link {
position: absolute;
top: -9999px;
left: 6px;
background: #ffffff;
color: #001a33;
padding: 0.75rem 1rem;
text-decoration: none;
z-index: 9999;
border-radius: 6px;
font-weight: 700;
border: 3px solid #001a33;
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.9);
transition: top 0.3s ease;
&:focus,
&:focus-visible {
top: 6px;
outline: 4px solid var(--color-accent-deco);
outline-offset: 3px;
color: #001a33;
background: #ffffff;
}
}
// Global button placeholder
%button-base {
@include button-base;
}
// Global card placeholder
%card-base {
border-radius: $border-radius-md;
box-shadow: var(--box-shadow-md);
@include card-hover;
}
// AAA Focus Management
%focus-aaa {
&:focus {
outline: 3px solid var(--color-focus-ring);
outline-offset: 2px;
border-radius: 4px;
}
}
// Apply AAA focus to all interactive elements
button,
a,
input,
select,
textarea,
[tabindex]:not([tabindex='-1']) {
@extend %focus-aaa;
}
// High contrast mode support
@media (prefers-contrast: high) {
* {
&:focus {
outline: 4px solid currentColor !important;
outline-offset: 2px !important;
}
}
button,
a {
border: 2px solid currentColor !important;
}
}
// Reduced motion support
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
// Touch target size for AAA (minimum 44x44px)
@media (pointer: coarse) {
button,
a,
input,
select,
textarea,
[role='button'] {
min-height: 44px;
min-width: 44px;
}
}
// Responsive mixins (keep existing)
@mixin mobile-only {
@media (max-width: #{$mobile-breakpoint - 1px}) {
@content;
}
}
@mixin desktop-only {
@media (min-width: $desktop-breakpoint) {
@content;
}
}
// Common button base styles
%button-base {
display: inline-flex;
align-items: center;
justify-content: center;
font-weight: 500;
cursor: pointer;
border: none;
border-radius: 8px;
transition: all 0.3s ease;
text-decoration: none;
&:focus {
outline: none;
}
&:active {
transform: translateY(0);
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none !important;
box-shadow: none !important;
}
}
// Interactive element hover effects
%hover-lift {
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px var(--box-shadow-hover);
}
}
// Clean button reset
%button-reset {
background: none;
border: none;
color: inherit;
cursor: pointer;
font: inherit;
}
// ── Scroll reveal animation ───────────────────────────────────────────────────
// Add class `reveal-item` to any element you want to animate in on scroll.
// The hook useScrollReveal() adds `is-visible` when the element enters view.
// Use `--reveal-delay` CSS custom property for staggered children.
.reveal-item {
opacity: 0;
transform: translateY(28px);
transition:
opacity 0.55s ease,
transform 0.55s ease;
transition-delay: var(--reveal-delay, 0ms);
&.is-visible {
opacity: 1;
transform: translateY(0);
}
}