@tailwind base; @tailwind components; @tailwind utilities; /* Dark theme (default) - CSS custom properties */ :root { --bg: #060b10; --bg2: #0c1520; --bg2-rgb: 12, 21, 32; --surface: #101e2d; --surface-rgb: 16, 30, 45; --border: #182840; --accent: #a855f7; --accent-rgb: 168, 85, 247; --accent2: #00ff88; --accent-glow: rgba(168, 85, 247, 0.12); --text: #c4d6e8; --text-dim: #7a9bb8; --muted: #304860; /* Article type colors - dark */ --type-tech: #a855f7; --type-life: #f59e0b; --type-quote: #00ff88; --type-link: #38bdf8; --type-photo: #ec4899; /* Article type text colors - dark (all black for WCAG AA) */ --type-tech-text: #000000; --type-life-text: #000000; --type-quote-text: #000000; --type-link-text: #000000; --type-photo-text: #000000; } /* Light theme overrides */ html.theme-light { --bg: #ffffff; --bg2: #f8f9fa; --bg2-rgb: 248, 249, 250; --surface: #f0f3f7; --surface-rgb: 240, 243, 247; --border: #d9dfe8; --accent: #9333ea; --accent-rgb: 147, 51, 234; --accent2: #10b981; --accent-glow: rgba(147, 51, 234, 0.1); --text: #1f2937; --text-dim: #374151; --muted: #d1d5db; /* Article type colors - light */ --type-tech: #7c3aed; --type-life: #d97706; --type-quote: #008f5a; --type-link: #0284c7; --type-photo: #be185d; /* Article type text colors - light (mixed for WCAG AA) */ --type-tech-text: #ffffff; --type-life-text: #000000; --type-quote-text: #000000; --type-link-text: #000000; --type-photo-text: #ffffff; } /* No-JS fallback: prefers-color-scheme light */ @media (prefers-color-scheme: light) { html:not(.theme-dark) { --bg: #ffffff; --bg2: #f8f9fa; --bg2-rgb: 248, 249, 250; --surface: #f0f3f7; --surface-rgb: 240, 243, 247; --border: #d9dfe8; --accent: #9333ea; --accent-rgb: 147, 51, 234; --accent2: #10b981; --accent-glow: rgba(147, 51, 234, 0.1); --text: #1f2937; --text-dim: #374151; --muted: #d1d5db; --type-tech: #7c3aed; --type-life: #d97706; --type-quote: #008f5a; --type-link: #0284c7; --type-photo: #be185d; --type-tech-text: #ffffff; --type-life-text: #000000; --type-quote-text: #000000; --type-link-text: #000000; --type-photo-text: #ffffff; } } /* Theme-aware picture element for default thumbnails */ html.theme-light picture img[src="/images/default_thumbnail_dark.png"] { content: url('/images/default_thumbnail_light.png'); } @layer base { html { @apply overflow-x-hidden; } body { @apply bg-bg text-text font-body overflow-x-hidden; } h1, h2, h3, h4, h5, h6 { @apply font-bold; font-family: 'Oxanium', monospace; } h1 { @apply text-3xl md:text-4xl; } h2 { @apply text-2xl md:text-3xl; } h3 { @apply text-xl md:text-2xl; } .heading-prefix { font-family: 'JetBrains Mono', monospace; font-weight: 400; font-size: 0.8em; color: var(--accent); opacity: 0.7; margin-right: 0.35em; user-select: none; } a { @apply text-accent hover:opacity-80 transition-opacity; } code { @apply font-mono bg-surface border border-border px-1.5 py-0.5 rounded text-accent2; } pre { background-color: rgba(var(--surface-rgb), 0.8); @apply p-4 rounded border border-border overflow-x-auto; } pre code { @apply bg-transparent border-0 p-0 text-text; } *:focus-visible { @apply ring-2 ring-accent ring-offset-2; ring-offset-color: var(--bg); } button, input, textarea, select { @apply transition-colors duration-200; } } @layer components { .container { @apply max-w-4xl mx-auto; } /* Background utilities */ .bg-bg { background-color: var(--bg); } .bg-bg2 { background-color: var(--bg2); } .bg-surface { background-color: var(--surface); } /* Border utilities */ .border-border { border-color: var(--border); } /* Text color utilities */ .text-accent { color: var(--accent); } .text-accent2 { color: var(--accent2); } .text-text { color: var(--text); } .text-text-dim { color: var(--text-dim); } /* Additional semantic utilities */ .text-muted { color: var(--muted); } .bg-muted { background-color: var(--muted); } /* Glow effect utility */ .glow-accent { box-shadow: 0 0 20px var(--accent-glow); } /* Frosted glass bar (header/footer) */ .frosted-bar { background-color: rgba(var(--bg2-rgb), 0.75); backdrop-filter: blur(10px); box-shadow: 0 0 20px var(--accent-glow); /* border applied via utility classes in templates */ } /* Border utilities for frosted-bar component */ .frosted-bar.border-b, .frosted-bar.border-t { border-color: var(--border); } /* Button component styles */ .btn { @apply inline-flex items-center justify-center px-4 py-2 rounded font-bold transition-all duration-200 cursor-pointer; background-color: var(--accent); color: #ffffff; border: none; outline: none; } .btn:hover:not(:disabled) { opacity: 0.85; transform: translateY(-1px); } .btn:focus-visible { @apply ring-2 ring-offset-2; ring-color: var(--accent); ring-offset-color: var(--bg); } .btn:active:not(:disabled) { transform: translateY(0); opacity: 0.75; } .btn:disabled { opacity: 0.5; cursor: not-allowed; } /* Button variants */ .btn-primary { background-color: var(--accent); color: #ffffff; } .btn-primary:hover:not(:disabled) { background-color: var(--accent); } .btn-secondary { background-color: var(--accent2); color: var(--bg); font-weight: 600; } .btn-secondary:hover:not(:disabled) { background-color: var(--accent2); } .btn-outline { background-color: transparent; color: var(--accent); border: 2px solid var(--accent); } .btn-outline:hover:not(:disabled) { background-color: var(--accent); color: #ffffff; } /* Button sizes */ .btn-sm { @apply px-3 py-1 text-sm; } .btn-lg { @apply px-6 py-3 text-lg; } /* Icon button (for icons without text) */ .btn-icon { @apply rounded-full inline-flex items-center justify-center; width: auto; height: auto; padding: 0.5rem; } .btn-icon svg, .btn-icon i { width: auto !important; height: auto !important; } /* Force Feather icons to match size */ .btn-icon [data-feather] { width: 50px !important; height: 50px !important; } .btn-icon [data-feather] svg { width: 50px !important; height: 50px !important; } /* Sidebar widget — no box, no border */ .sidebar-widget { margin-bottom: 1.5rem; } /* Sidebar widget title — bash comment style */ .sidebar-widget-label { font-family: var(--font-mono, monospace); font-size: 1rem; font-weight: bold; color: var(--accent); letter-spacing: 0.08em; margin-bottom: 0.5rem; } /* Sidebar separator */ .sidebar-hr { border: none; border-top: 1px solid var(--border); margin-bottom: 1.5rem; } /* ===================== Tag Cloud Component ===================== */ .tag-cloud { display: flex; flex-wrap: wrap; justify-content: center; gap: 0.75rem; align-items: baseline; overflow: visible; } .tag-cloud-link { display: inline-flex; align-items: center; gap: 0.375rem; padding: 0.25rem 0.625rem; border: 1px solid var(--border); border-radius: 0.25rem; font-family: var(--font-mono, 'JetBrains Mono', monospace); color: var(--text-dim); text-decoration: none; background-color: var(--bg2); transition: border-color 150ms ease-out, color 150ms ease-out, background-color 150ms ease-out, opacity 150ms ease-out; white-space: nowrap; line-height: 1.4; } .tag-cloud-link:hover { border-color: rgba(var(--accent-rgb), 0.5); color: var(--accent); background-color: rgba(var(--accent-rgb), 0.1); } .tag-cloud-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 0.25rem; } .tag-cloud-count { display: inline-flex; align-items: center; justify-content: center; padding: 0 0.375rem; border-radius: 9999px; font-size: 0.65em; font-weight: 600; background-color: rgba(var(--accent-rgb), 0.12); color: var(--accent); line-height: 1.6; min-width: 1.2em; } @media (prefers-reduced-motion: reduce) { .tag-cloud-link { transition: none; } } .share-grid { display: grid; grid-template-columns: repeat(3, 50px); justify-content: space-evenly; justify-items: center; align-content: space-evenly; align-items: center; } .btn-share { display: inline-flex; align-items: center; justify-content: center; width: 50px; height: 50px; border-radius: 4px; border: 1px solid var(--border); background: var(--surface); color: var(--text-dim); cursor: pointer; transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease; text-decoration: none; } .btn-share svg, .btn-share i, .btn-share [data-feather] { width: 22px !important; height: 22px !important; flex-shrink: 0; } .btn-share:hover { color: var(--accent); border-color: rgba(var(--accent-rgb), 0.5); background: rgba(var(--accent-rgb), 0.06); box-shadow: 0 0 10px var(--accent-glow); } .btn-share:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; color: var(--accent); border-color: var(--accent); } .btn-share--copied { color: var(--accent2); border-color: var(--accent2); background: var(--surface); } @media (prefers-reduced-motion: reduce) { .btn-share { transition: none; } } /* Badge base style */ .badge { @apply inline-flex items-center px-2.5 py-1 rounded text-sm font-mono font-semibold whitespace-nowrap transition-all duration-200; border: 1px solid; } /* Article type badge styles */ .badge-tech { color: var(--type-tech); background-color: rgba(168, 85, 247, 0.1); border-color: rgba(168, 85, 247, 0.3); } .badge-tech:hover { background-color: rgba(168, 85, 247, 0.2); } .badge-life { color: var(--type-life); background-color: rgba(245, 158, 11, 0.1); border-color: rgba(245, 158, 11, 0.3); } .badge-life:hover { background-color: rgba(245, 158, 11, 0.2); } .badge-quote { color: var(--type-quote); background-color: rgba(0, 255, 136, 0.1); border-color: rgba(0, 255, 136, 0.3); } .badge-quote:hover { background-color: rgba(0, 255, 136, 0.2); } .badge-link { color: var(--type-link); background-color: rgba(56, 189, 248, 0.1); border-color: rgba(56, 189, 248, 0.3); } .badge-link:hover { background-color: rgba(56, 189, 248, 0.2); } .badge-photo { color: var(--type-photo); background-color: rgba(236, 72, 153, 0.1); border-color: rgba(236, 72, 153, 0.3); } .badge-photo:hover { background-color: rgba(236, 72, 153, 0.2); } /* Legacy type-* classes for compatibility (with badge styling) */ .type-tech { color: var(--type-tech); background-color: rgba(168, 85, 247, 0.1); } .type-life { color: var(--type-life); background-color: rgba(245, 158, 11, 0.1); } .type-quote { color: var(--type-quote); background-color: rgba(0, 255, 136, 0.1); } .type-link { color: var(--type-link); background-color: rgba(56, 189, 248, 0.1); } .type-photo { color: var(--type-photo); background-color: rgba(236, 72, 153, 0.1); } /* Card component */ .card { @apply border border-border rounded-lg overflow-hidden transition-all duration-200; box-shadow: 0 0 20px var(--accent-glow); } .card:hover { transform: translateY(-2px); box-shadow: 0 0 30px var(--accent-glow); } .card-image { @apply aspect-video object-cover w-full; } .card-body { @apply p-5 md:p-6 space-y-3; } .card-title { @apply text-xl font-semibold; } .card-excerpt { @apply text-text-dim text-sm line-clamp-3; } .card-footer { @apply flex items-center justify-between gap-4; } /* ===================== Repository Grid ===================== */ .repo-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; } .repo-card:only-child { max-width: 50%; margin-inline: auto; } /* ===================== Timeline Layout ===================== */ .timeline { @apply relative mx-auto max-w-5xl px-4 py-12; } /* Vertical spine */ .timeline::before { content: ''; @apply absolute top-0 bottom-0; /* Mobile: fixed 20px from container left edge */ left: 20px; width: 2px; background: linear-gradient(to bottom, var(--accent), var(--accent2, var(--accent))); opacity: 0.7; } @screen md { .timeline::before { left: 50%; transform: translateX(-50%); } } /* Each timeline row — block, full width, positioned context for abs children */ .timeline-item { @apply relative mb-10; /* Mobile geometry (.timeline has px-4 = 16px padding): spine left = 20px from
    border → 20-16 = 4px from item's containing block. We want card to start ~8px right of spine right edge (22px from
      = 6px from block). Use margin-left: 30px so card starts at 16+30=46px from
        , 24px right of spine right. abs children: spine left relative to item = 4 - 30 = -26px. */ margin-left: 30px; } @screen md { .timeline-item { margin-left: 0; } } /* ---- Connector line ---- */ .timeline-connector { @apply absolute; top: 20px; height: 2px; /* Mobile: .timeline px-4(16px) + item margin-left(30px) = 46px from
          border. Spine right edge = 22px from
            border = 22-46 = -24px from item left. Connector: left=-24px, width=24px → touches spine right and card left edge. */ left: -24px; width: 24px; } @screen md { /* spine center = 50% of item. Spine is 2px: left edge at 50%-1px, right at 50%+1px. Cards occupy [0 .. 50%-24px] (left) or [50%+24px .. 100%] (right). Left connector: left=50%-24px, width=23px → touches card right edge and spine left. Right connector: left=50%+1px, width=23px → touches spine right and card left. */ .timeline-item--left .timeline-connector { left: calc(50% - 24px); width: 23px; } .timeline-item--right .timeline-connector { left: calc(50% + 1px); width: 23px; } } /* ---- Node on spine ---- */ .timeline-node { @apply absolute rounded-full z-10; top: 14px; /* Mobile: spine center = 21px from
              border = 21-46 = -25px from item left. Node (10px wide): left = -25 - 5 = -30px. */ left: -30px; width: 10px; height: 10px; border: 2px solid var(--bg); } @screen md { .timeline-node { width: 12px; height: 12px; top: 14px; /* Desktop: spine center = 50% of item. Node center on spine: left = 50% - 6px */ left: calc(50% - 6px); } } /* ---- Card wrapper ---- */ .timeline-card { @apply flex border rounded-lg overflow-hidden bg-surface; /* Mobile: column (thumb on top) */ flex-direction: column; width: 100%; transition: box-shadow 0.2s, transform 0.2s; } .timeline-card:hover { transform: translateY(-2px); } @screen md { /* Desktop: push card into left or right half via item padding */ .timeline-item--left { padding-right: calc(50% + 24px); } .timeline-item--right { padding-left: calc(50% + 24px); } /* Left card: thumb outer-left, body inner-right */ .timeline-item--left .timeline-card { flex-direction: row; } /* Right card: body inner-left, thumb outer-right */ .timeline-item--right .timeline-card { flex-direction: row-reverse; } } /* ---- Thumbnail panel ---- */ .timeline-thumb { @apply flex-shrink-0 overflow-hidden; /* Mobile: full-width banner */ width: 100%; height: 90px; } @screen md { .timeline-thumb { /* 3:2 landscape: wider than tall */ width: 40%; height: auto; aspect-ratio: 3 / 2; } } .timeline-thumb img { @apply w-full h-full object-cover; transition: transform 0.2s; } .timeline-card:hover .timeline-thumb img { transform: scale(1.03); } /* ---- Text panel ---- */ .timeline-body { @apply flex flex-col gap-2 p-4 flex-1; } /* ---- Meta row (TYPE · date) ---- */ .timeline-meta { @apply flex items-center gap-2 text-xs font-mono tracking-widest uppercase; } .timeline-meta-sep { @apply text-border; } .timeline-date { @apply text-text-dim normal-case tracking-normal; } /* ---- Title ---- */ .timeline-title { @apply font-semibold text-base leading-snug; } .timeline-title a { @apply hover:text-accent transition-colors; } /* ---- Excerpt ---- */ .timeline-excerpt { @apply text-text-dim text-sm line-clamp-3 leading-relaxed; } /* ---- Pinned badge ---- */ .timeline-pinned { @apply inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs font-semibold; } /* ---- Type-color variants (node, connector, card) ---- */ /* Tech (purple) */ .timeline-node--tech { background-color: var(--type-tech); box-shadow: 0 0 12px color-mix(in srgb, var(--type-tech) 40%, transparent); } .timeline-connector--tech { background-color: var(--type-tech); } .timeline-card--tech { border-color: color-mix(in srgb, var(--type-tech) 25%, transparent); box-shadow: 0 0 18px color-mix(in srgb, var(--type-tech) 10%, transparent); } /* Life (amber) */ .timeline-node--life { background-color: var(--type-life); box-shadow: 0 0 12px color-mix(in srgb, var(--type-life) 40%, transparent); } .timeline-connector--life { background-color: var(--type-life); } .timeline-card--life { border-color: color-mix(in srgb, var(--type-life) 25%, transparent); box-shadow: 0 0 18px color-mix(in srgb, var(--type-life) 10%, transparent); } /* Quote (green) */ .timeline-node--quote { background-color: var(--type-quote); box-shadow: 0 0 12px color-mix(in srgb, var(--type-quote) 40%, transparent); } .timeline-connector--quote { background-color: var(--type-quote); } .timeline-card--quote { border-color: color-mix(in srgb, var(--type-quote) 25%, transparent); box-shadow: 0 0 18px color-mix(in srgb, var(--type-quote) 10%, transparent); } /* Link (cyan) */ .timeline-node--link { background-color: var(--type-link); box-shadow: 0 0 12px color-mix(in srgb, var(--type-link) 40%, transparent); } .timeline-connector--link { background-color: var(--type-link); } .timeline-card--link { border-color: color-mix(in srgb, var(--type-link) 25%, transparent); box-shadow: 0 0 18px color-mix(in srgb, var(--type-link) 10%, transparent); } /* Photo (pink) */ .timeline-node--photo { background-color: var(--type-photo); box-shadow: 0 0 12px color-mix(in srgb, var(--type-photo) 40%, transparent); } .timeline-connector--photo { background-color: var(--type-photo); } .timeline-card--photo { border-color: color-mix(in srgb, var(--type-photo) 25%, transparent); box-shadow: 0 0 18px color-mix(in srgb, var(--type-photo) 10%, transparent); } /* ---- Timeline lazy-reveal (scroll-triggered) ---- */ .js-lazy-timeline > .timeline-item { opacity: 0; transform: translateX(-18px); transition: opacity 320ms ease-out, transform 320ms ease-out; } @screen md { .js-lazy-timeline > .timeline-item--right { transform: translateX(18px); } } .js-lazy-timeline > .timeline-item.is-visible { opacity: 1; transform: translateX(0); } /* Header navigation styling */ .header { @apply fixed top-0 left-0 right-0 z-40; } .header-nav { @apply hidden md:flex items-center gap-6; } .nav-link { @apply text-text hover:text-accent transition-colors; } .header-actions { @apply flex items-center gap-4; } /* Mobile menu overlay */ .menu-overlay { @apply fixed inset-0 bg-bg z-40 opacity-0 invisible transition-all duration-300; } .menu-overlay.active { @apply opacity-100 visible; } .menu-nav { @apply flex flex-col gap-4 p-6 text-lg font-semibold; } .menu-nav a { @apply text-text hover:text-accent transition-colors; } /* Breadcrumb navigation */ .breadcrumb { @apply flex items-center gap-2 text-sm text-text-dim; } .breadcrumb a { @apply hover:text-accent transition-colors; } .breadcrumb-separator { @apply opacity-50; } /* Article metadata styling (with icons) */ .article-meta { @apply flex flex-wrap items-center gap-4 text-sm text-text-dim; } .article-meta-item { @apply flex items-center gap-2; } .article-meta-item i { @apply w-4 h-4 flex-shrink-0; color: var(--accent2); } /* Hero typography with fluid sizing */ .hero-title { font-size: clamp(2rem, 5vw + 1rem, 4.5rem); } .section-title { font-size: clamp(1.5rem, 3vw + 0.5rem, 2.5rem); } /* ---- Article prev/next navigation ---- */ .article-nav { } .article-nav-prompt { @apply font-mono text-sm mb-2; color: var(--accent); } .article-nav-links { @apply flex flex-col md:flex-row md:justify-between md:items-center gap-4 md:gap-0 font-mono text-sm; } .article-nav-link { @apply hover:text-accent transition-colors text-text; } .article-nav-placeholder { @apply text-text-dim opacity-40; } /* ---- Footer badge variants ---- */ .badge-footer-accent { @apply inline-flex items-center px-2.5 py-1 rounded text-xs font-mono font-semibold whitespace-nowrap; border: 1px solid rgba(168, 85, 247, 0.35); background: rgba(168, 85, 247, 0.1); color: var(--accent); } .badge-footer-accent2 { @apply inline-flex items-center px-2.5 py-1 rounded text-xs font-mono font-semibold whitespace-nowrap; border: 1px solid rgba(0, 255, 136, 0.35); background: rgba(0, 255, 136, 0.1); color: var(--accent2); } /* Back to top button */ .back-to-top { @apply fixed bottom-6 right-6 z-40 w-11 h-11 rounded-full flex items-center justify-center; background: var(--accent); box-shadow: 0 0 12px rgba(var(--accent-rgb), 0.4); transition: background 200ms ease, box-shadow 200ms ease; color: #fff; } .back-to-top:hover { background: var(--accent); filter: brightness(0.85); box-shadow: 0 0 20px var(--accent); } .back-to-top:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } } /* Prose overrides for light theme */ html.theme-light .prose, html.theme-light .prose-invert { color: var(--text); } html.theme-light .prose a, html.theme-light .prose-invert a { color: var(--accent); } html.theme-light .prose strong, html.theme-light .prose-invert strong { color: var(--text); } html.theme-light .prose code, html.theme-light .prose-invert code { color: var(--accent2); } html.theme-light .prose pre, html.theme-light .prose-invert pre { background-color: var(--surface); color: var(--text); } html.theme-light .prose h1, html.theme-light .prose h2, html.theme-light .prose h3, html.theme-light .prose h4, html.theme-light .prose h5, html.theme-light .prose h6, html.theme-light .prose-invert h1, html.theme-light .prose-invert h2, html.theme-light .prose-invert h3, html.theme-light .prose-invert h4, html.theme-light .prose-invert h5, html.theme-light .prose-invert h6 { color: var(--text); } html.theme-light .prose blockquote, html.theme-light .prose-invert blockquote { color: var(--text); border-left-color: var(--accent); } /* Responsive container utilities - mobile-first */ .container { @apply max-w-full px-4; } @media (min-width: 768px) { .container { @apply max-w-4xl px-6; } } @media (min-width: 1060px) { .container { @apply max-w-5xl px-8; } } /* Alpine.js x-cloak - hide content until Alpine initializes */ [x-cloak] { display: none !important; } /* Respect user's motion preferences */ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; } } /* Matrix rain canvas background */ #matrix-rain { position: fixed; inset: 0; width: 100%; height: 100%; pointer-events: none; z-index: 1; } /* Dark theme: 13% opacity (inner pages) */ html.theme-dark #matrix-rain { opacity: 0.13; } /* Light theme: 18% opacity (inner pages) */ html.theme-light #matrix-rain { opacity: 0.18; } /* Homepage: more prominent background */ html.theme-dark body[data-page-kind="home"] #matrix-rain { opacity: 0.28; } html.theme-light body[data-page-kind="home"] #matrix-rain { opacity: 0.35; } /* Reduced motion: hide canvas entirely */ @media (prefers-reduced-motion: reduce) { #matrix-rain { display: none; } } /* Content grid background — blocks rain under text, visible in gutters (single pages only) */ .content-grid { position: relative; z-index: 10; background-color: var(--bg); padding: 2px; border: 1px solid var(--border); box-shadow: 0 0 20px var(--accent-glow); } @media (min-width: 768px) { .content-grid { padding: 2rem; } } /* Article list items — soft glow effect */ article.border.border-border\/30.rounded-lg.overflow-hidden.group.bg-bg { border-color: var(--border); box-shadow: 0 0 20px var(--accent-glow); } /* ============================================ FORM COMPONENTS (Week 4) ============================================ */ /* Form input base styles */ .form-input, .form-textarea, .form-select { @apply w-full px-4 py-2 rounded border border-border bg-bg2 text-text font-body transition-all duration-200; } /* Input placeholder styling */ .form-input::placeholder, .form-textarea::placeholder { color: var(--text-dim); opacity: 0.7; } /* Input focus state */ .form-input:focus, .form-textarea:focus, .form-select:focus { @apply outline-none border-accent ring-2 ring-accent ring-offset-2; ring-offset-color: var(--bg); border-color: var(--accent); } /* Input invalid/error state */ .form-input:invalid, .form-textarea:invalid, .form-select:invalid, .form-input.error, .form-textarea.error, .form-select.error { @apply border-red-500 ring-red-500; } .form-input:invalid:focus, .form-textarea:invalid:focus, .form-select:invalid:focus { @apply ring-red-500 border-red-500; ring-offset-color: var(--bg); } /* Input disabled state */ .form-input:disabled, .form-textarea:disabled, .form-select:disabled { @apply opacity-50 cursor-not-allowed bg-muted; } /* Textarea specific */ .form-textarea { @apply min-h-32; resize: vertical; } .form-textarea.auto-expand { resize: none; overflow-y: hidden; } /* Select dropdown */ .form-select { cursor: pointer; appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23a855f7' d='M6 9L1 4h10z'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 0.75rem center; padding-right: 2.5rem; } html.theme-light .form-select { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%239333ea' d='M6 9L1 4h10z'/%3E%3C/svg%3E"); } /* Checkbox and radio button base */ .form-checkbox, .form-radio { @apply w-5 h-5 cursor-pointer accent-accent transition-all duration-200; appearance: none; border: 2px solid var(--border); border-radius: 0.375rem; flex-shrink: 0; } .form-radio { border-radius: 50%; } /* Checkbox/radio focus state */ .form-checkbox:focus-visible, .form-radio:focus-visible { @apply outline-none ring-2 ring-accent ring-offset-2; ring-offset-color: var(--bg); } /* Checkbox/radio checked state */ .form-checkbox:checked, .form-radio:checked { background-color: var(--accent); border-color: var(--accent); background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: center; background-size: 100%; } .form-radio:checked { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='8' cy='8' r='3.5'/%3E%3C/svg%3E"); } /* Checkbox/radio disabled state */ .form-checkbox:disabled, .form-radio:disabled { @apply opacity-50 cursor-not-allowed; border-color: var(--muted); } /* Form group layout */ .form-group { @apply space-y-2; } .form-group label { @apply block text-sm font-semibold text-text; } .form-group.required label::after { content: ' *'; color: #ef4444; } .form-group-input { @apply relative; } .form-group-help { @apply text-xs text-text-dim; } .form-group.error .form-group-help { @apply text-red-500; } .form-error { @apply text-sm text-red-500 mt-1; } /* Form layout utilities */ .form-row { @apply flex flex-col md:flex-row gap-4; } .form-row > .form-group { @apply flex-1; } .form-inline { @apply flex flex-col sm:flex-row items-end gap-4; } .form-inline .form-group { @apply flex-1; } .form-inline .btn { @apply h-10; } /* Character count indicator */ .form-char-count { @apply text-xs text-text-dim text-right; } .form-char-count.warning { @apply text-amber-500; } .form-char-count.error { @apply text-red-500; } /* ============================================ FOCUS MANAGEMENT (Week 5) ============================================ */ /* Enhanced :focus-visible with accent color styling */ :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } /* Respect motion preferences for focus indicator */ @media (prefers-reduced-motion: reduce) { :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } } /* Button and link hover/focus transitions */ button, a.btn, .btn { transition: all 150ms ease-out; } button:hover, a.btn:hover, .btn:hover { opacity: 0.8; transform: translateY(-1px); } button:active, a.btn:active, .btn:active { transform: translateY(0); } /* Form input focus transitions with glow effect */ input, textarea, select { transition: all 200ms ease-out; } input:focus, textarea:focus, select:focus, input:focus-visible, textarea:focus-visible, select:focus-visible { box-shadow: 0 0 0 3px rgba(var(--accent-rgb), 0.1); } /* ============================================ MODAL COMPONENTS (Week 4) ============================================ */ /* Modal backdrop */ .modal-backdrop { @apply fixed inset-0 bg-black/50 opacity-0 invisible transition-all duration-300 z-40; backdrop-filter: blur(2px); } .modal-backdrop.active { @apply opacity-100 visible; } /* Modal container */ .modal { @apply fixed inset-0 flex items-center justify-center opacity-0 invisible transition-all duration-300 z-50 p-4; pointer-events: none; } .modal.active { @apply opacity-100 visible; pointer-events: auto; } .modal.active .modal-backdrop { @apply opacity-100 visible; pointer-events: auto; } /* Modal content box */ .modal-content { @apply bg-bg2 border border-border rounded-lg shadow-2xl max-w-lg w-full max-h-[90vh] flex flex-col; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); animation: modalSlideUp 0.3s ease-out; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes slideUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } @keyframes modalSlideUp { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } } /* Modal header */ .modal-header { @apply flex items-start justify-between gap-4 p-6 border-b border-border; } .modal-title { @apply text-xl font-bold text-text; } .modal-close { @apply flex items-center justify-center w-8 h-8 rounded hover:bg-surface cursor-pointer transition-colors; } .modal-close::before, .modal-close::after { content: ''; @apply absolute w-5 h-0.5 bg-text-dim; } .modal-close::before { transform: rotate(45deg); } .modal-close::after { transform: rotate(-45deg); } .modal-close:hover::before, .modal-close:hover::after { @apply bg-accent; } /* Modal body */ .modal-body { @apply flex-1 overflow-y-auto p-6 space-y-4; } /* Modal footer */ .modal-footer { @apply flex items-center justify-end gap-3 p-6 border-t border-border; } /* Modal sizes */ .modal-content.modal-sm { @apply max-w-sm; } .modal-content.modal-md { @apply max-w-md; } .modal-content.modal-lg { @apply max-w-2xl; } /* Modal variants */ .modal-content.modal-alert { @apply max-w-sm; } .modal-content.modal-confirm { @apply max-w-sm; } /* Modal button styling */ .modal-footer .btn { @apply min-w-[100px]; } /* ============================================ INTERACTIVE PATTERNS (Week 4) ============================================ */ /* Loading spinner */ .spinner { @apply inline-block; width: 1rem; height: 1rem; border: 2px solid var(--border); border-top-color: var(--accent); border-radius: 50%; animation: spin 0.6s linear infinite; } @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } .spinner-sm { width: 0.75rem; height: 0.75rem; border-width: 1.5px; } .spinner-lg { width: 1.5rem; height: 1.5rem; border-width: 3px; } /* Button with spinner */ .btn:disabled .spinner { @apply inline-block mr-2; } /* Toast notification container */ .toast-container { @apply fixed bottom-0 right-0 p-4 space-y-3 max-w-sm z-50; } @media (max-width: 640px) { .toast-container { @apply left-0 right-0 px-4; } } /* Toast base */ .toast { @apply flex items-start gap-3 p-4 rounded-lg border border-border shadow-lg; animation: slideInUp 0.3s ease-out; background-color: var(--bg2); color: var(--text); } @keyframes slideInUp { from { transform: translateY(100%); opacity: 0; } to { transform: translateY(0); opacity: 1; } } /* Animation Utility Classes */ .animate-fade-in { animation: fadeIn 300ms ease-out; } .animate-slide-up { animation: slideUp 300ms ease-out; } .animate-spin-loader { animation: spin 600ms linear infinite; } /* Toast variants */ .toast-success { border-color: #10b981; background-color: rgba(16, 185, 129, 0.1); } .toast-success::before { content: '✓'; color: #10b981; font-weight: bold; flex-shrink: 0; } .toast-error { border-color: #ef4444; background-color: rgba(239, 68, 68, 0.1); } .toast-error::before { content: '✕'; color: #ef4444; font-weight: bold; flex-shrink: 0; } .toast-info { border-color: #3b82f6; background-color: rgba(59, 130, 246, 0.1); } .toast-info::before { content: 'ℹ'; color: #3b82f6; flex-shrink: 0; } .toast-warning { border-color: #f59e0b; background-color: rgba(245, 158, 11, 0.1); } .toast-warning::before { content: '⚠'; color: #f59e0b; flex-shrink: 0; } .toast-close { @apply ml-auto flex-shrink-0 w-6 h-6 flex items-center justify-center cursor-pointer hover:bg-surface rounded transition-colors; } /* Tooltip */ .tooltip { @apply relative inline-block; } .tooltip-text { @apply absolute bg-bg2 text-text-dim text-xs rounded px-2 py-1 whitespace-nowrap pointer-events-none opacity-0 invisible transition-all duration-200; z-index: 50; bottom: 125%; left: 50%; transform: translateX(-50%); } .tooltip:hover .tooltip-text { @apply opacity-100 visible; } .tooltip-text::after { content: ''; @apply absolute w-2 h-2 bg-bg2; bottom: -4px; left: 50%; transform: translateX(-50%) rotate(45deg); } /* Tooltip directions */ .tooltip-bottom .tooltip-text { @apply top-[125%] bottom-auto; } .tooltip-bottom .tooltip-text::after { @apply top-[-4px] bottom-auto; transform: translateX(-50%) rotate(225deg); } .tooltip-left .tooltip-text { @apply left-auto right-[125%]; transform: none; } .tooltip-left .tooltip-text::after { @apply left-auto right-[-4px]; transform: rotate(135deg); } .tooltip-right .tooltip-text { @apply left-[125%]; transform: none; } .tooltip-right .tooltip-text::after { @apply left-[-4px] right-auto; transform: rotate(315deg); } /* Obsolete article banner */ .banner-obsolete { margin: 1.5rem 0; padding: 1rem 1.25rem; border-left: 4px solid var(--type-life); border-radius: 0.5rem; background-color: rgba(245, 158, 11, 0.08); } .banner-obsolete__inner { display: flex; align-items: flex-start; gap: 0.75rem; color: var(--type-life); } .banner-obsolete__inner svg { flex-shrink: 0; margin-top: 0.125rem; color: var(--type-life); } .banner-obsolete__body { display: flex; flex-direction: column; gap: 0.75rem; flex: 1; } .banner-obsolete__body p { color: var(--text); font-size: 0.9rem; margin: 0; } .banner-obsolete__cta { display: inline-flex; align-items: center; gap: 0.5rem; align-self: center; padding: 0.4rem 1rem; border: 1px solid var(--type-life); border-radius: 0.375rem; color: var(--type-life); font-size: 0.875rem; font-weight: 500; text-decoration: none; transition: background-color 0.2s; } .banner-obsolete__cta:hover { background-color: rgba(245, 158, 11, 0.12); } /* Light theme override — #d97706 passes WCAG AA on white */ html.theme-light .banner-obsolete { background-color: rgba(217, 119, 6, 0.07); border-left-color: var(--type-life); } /* Motion Safety - Respect prefers-reduced-motion */ @media (prefers-reduced-motion: reduce) { /* Remove all animations */ *, *::before, *::after { animation: none !important; transition: none !important; } /* Ensure focus-visible is still visible */ :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } }