// ============================================================================= // abstracts/_mixins.scss // Responsive helpers and reusable patterns. // ============================================================================= // Bulma-matching breakpoint mixins. @mixin mobile { @media screen and (max-width: 768px) { @content; } } @mixin tablet { @media screen and (min-width: 769px) { @content; } } @mixin tablet-only { @media screen and (min-width: 769px) and (max-width: 1023px) { @content; } } @mixin desktop { @media screen and (min-width: 1024px) { @content; } } @mixin widescreen { @media screen and (min-width: 1216px) { @content; } } // Truncate text to N lines using -webkit-line-clamp. @mixin line-clamp($lines: 2) { display: -webkit-box; -webkit-line-clamp: $lines; -webkit-box-orient: vertical; overflow: hidden; } // Visually hidden (accessible). @mixin visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; } // Aspect-ratio box. @mixin aspect-ratio($w, $h) { position: relative; &::before { content: ''; display: block; padding-top: percentage($h / $w); } > * { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; } } // Focus visible ring (WCAG 2.1). @mixin focus-ring($color: var(--color-primary)) { &:focus-visible { outline: 3px solid $color; outline-offset: 2px; } } // Card elevation with hover lift. @mixin card-lift { transition: transform var(--transition-base), box-shadow var(--transition-base); &:hover { transform: translateY(-4px); box-shadow: var(--card-shadow-hover); } } // Skeleton loading shimmer. @mixin skeleton-shimmer { background: linear-gradient( 90deg, var(--color-surface) 25%, rgba(255, 255, 255, 0.5) 50%, var(--color-surface) 75% ); background-size: 200% 100%; animation: skeleton-shimmer 1.5s infinite; } @keyframes skeleton-shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }