html {
    color-scheme: dark;
}

/* ── Back to Top Button ─────────────────────────────────────────── */
#back-to-top {
    position: fixed;
    bottom: 2rem;
    right: 2rem;
    width: 3.25rem;
    height: 3.25rem;
    border-radius: 50%;
    /* Match site theme: sleek white glass */
    background: linear-gradient(180deg, rgba(255, 255, 255, 0.08) 0%, rgba(255, 255, 255, 0.03) 100%);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border: 1px solid rgba(255, 255, 255, 0.1) !important;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #ffffff;
    cursor: pointer;
    z-index: 40;
    opacity: 0;
    pointer-events: none;
    transform: translateY(20px) translateZ(0);
    transition:
        opacity 0.4s var(--ease-out),
        transform 0.4s var(--ease-out),
        background 0.4s var(--ease-out),
        border-color 0.4s var(--ease-out),
        box-shadow 0.4s var(--ease-out);
    box-shadow:
        0 1px 2px rgba(0, 0, 0, 0.2),
        inset 0 1px 0 rgba(255, 255, 255, 0.1);
    overflow: hidden;
    /* Hide icon when it flies out */
}

#back-to-top.visible {
    opacity: 1;
    pointer-events: auto;
    transform: translateY(0) translateZ(0);
}

@media (hover: hover) {
    #back-to-top:hover {
        background: linear-gradient(180deg, rgba(255, 255, 255, 0.12) 0%, rgba(255, 255, 255, 0.05) 100%);
        border-color: rgba(255, 255, 255, 0.15) !important;
        transform: translateY(0) translateZ(0);
        /* No jumping */
        box-shadow:
            0 1px 2px rgba(0, 0, 0, 0.2),
            inset 0 1px 0 rgba(255, 255, 255, 0.15);
    }

    #back-to-top:hover i:not(.fly-animation i) {
        transform: translateY(-2px);
    }
}

#back-to-top i {
    transition: transform 0.2s ease-in-out;
}

/* Fly Up Animation */
.fly-animation i {
    animation: fly-up-in 0.8s var(--ease-spring) forwards;
}

@keyframes fly-up-in {
    0% {
        transform: translateY(0);
    }

    30% {
        transform: translateY(-40px);
        /* Fly out top quickly */
        opacity: 0;
    }

    31% {
        transform: translateY(40px);
        /* Instantly move to bottom */
        opacity: 0;
    }

    60% {
        opacity: 1;
    }

    100% {
        transform: translateY(0);
        /* Smoothly slide back into place */
    }
}

@font-face {
    font-family: "Poppins";
    src: url("../fonts/Poppins-Light.24270b57ca53.woff2") format("woff2");
    font-weight: 300;
    font-display: swap;
}

@font-face {
    font-family: "Poppins";
    src: url("../fonts/Poppins-Regular.a06144cc7e66.woff2") format("woff2");
    font-weight: 400;
    font-display: swap;
}

@font-face {
    font-family: "Poppins";
    src: url("../fonts/Poppins-Medium.d490c3a24459.woff2") format("woff2");
    font-weight: 500;
    font-display: swap;
}

@font-face {
    font-family: "Poppins";
    src: url("../fonts/Poppins-SemiBold.a18102c13057.woff2") format("woff2");
    font-weight: 600;
    font-display: swap;
}

@font-face {
    font-family: "Poppins";
    src: url("../fonts/Poppins-Bold.d1583e8c8084.woff2") format("woff2");
    font-weight: 700;
    font-display: swap;
}

@font-face {
    font-family: "Poppins";
    src: url("../fonts/Poppins-ExtraBold.af4d371a1027.woff2") format("woff2");
    font-weight: 800;
    font-display: swap;
}

@font-face {
    font-family: "Poppins";
    src: url("../fonts/Poppins-Black.5426bf50c845.woff2") format("woff2");
    font-weight: 900;
    font-display: swap;
}

html {
    overflow-anchor: none;
    /* Dark theme: native scrollbars, form controls and <select> popups
       render dark instead of flashing white (Windows dark mode fix). */
    color-scheme: dark;
    scrollbar-width: thin;
    /* Track stays gray-900 to match the Tailwind page background; the
       thumb moves to the slate ramp so it agrees with the text colours. */
    scrollbar-color: #334155 #111827;
}

/* Custom Scrollbar for WebKit */
::-webkit-scrollbar {
    width: 10px;
    height: 10px;
}

::-webkit-scrollbar-track {
    background: #111827;
}

::-webkit-scrollbar-thumb {
    background: #334155;
    border-radius: 5px;
    border: 2px solid #111827;
    transition: background var(--dur-fast) ease;
}

::-webkit-scrollbar-thumb:hover {
    background: var(--text-dim);
}

/* Premium Text Selection */
::selection {
    background: var(--accent-select);
    color: #ffffff;
}

::-moz-selection {
    background: var(--accent-select);
    color: #ffffff;
}

body {
    font-family: Poppins, sans-serif;
}

/* Balance heading lines so the last line never ends with a single word
   (widow). Tailwind <3.4 has no text-balance utility, so define it here;
   templates already use the class. */
.text-balance {
    text-wrap: balance;
}

/* ── Skip link ──────────────────────────────────────────────────────
   First focusable element on the page; hidden off-canvas until keyboard
   focus lands on it, then slides in above the sticky nav. */
.skip-link {
    position: fixed;
    top: 1rem;
    left: 1rem;
    z-index: 100;
    padding: 0.625rem 1rem;
    border-radius: 0.5rem;
    background: #0f172a;
    color: #ffffff;
    border: 1px solid var(--surface-border-hover);
    box-shadow: var(--surface-shadow);
    font-size: 0.875rem;
    font-weight: 600;
    text-decoration: none;
    /* Compositor-friendly: hide with transform, not layout properties */
    transform: translateY(calc(-100% - 2rem));
    transition: transform 0.2s var(--ease-out);
}

.skip-link:focus {
    transform: translateY(0);
}

/* The mobile menu and topic-underline link animate only transform/
   opacity/background-size, never layout properties — pin the blanket
   Tailwind transition-all to the properties actually used. These plain
   rules win over the utility because base.css loads after dist. */
.mob-nav {
    transition-property: transform, opacity;
    /* Floating panel elevation — matches the search modal / back-to-top
       so the menu reads as a layer hovering above the page, not a flat
       rectangle pinned to the nav. The inset 1px white ring reproduces the
       template's ring-1 ring-inset ring-white/5 utility, which sets
       box-shadow too and would otherwise be replaced by this later rule. */
    box-shadow:
        0 12px 32px rgba(0, 0, 0, 0.35),
        0 0 20px rgba(6, 182, 212, 0.04),
        inset 0 0 0 1px rgba(255, 255, 255, 0.05);
}

.transition-underline {
    transition: background-size 0.5s ease-out, color 0.3s ease-out;
}

* {
    -webkit-tap-highlight-color: transparent !important;
    /* iOS/Android: kill the default grey/blue flash on every tap.
       Our own :active + .tapped animations provide the feedback instead. */
}



.animate-fade-scale {
    animation: fadeScale 0.25s var(--ease-out) forwards;
}

@keyframes fadeScale {
    from {
        opacity: 0;
        transform: scale(0.96) translateY(10px);
    }

    to {
        opacity: 1;
        transform: scale(1) translateY(0);
    }
}

.animate-fade-in {
    animation: fadeIn 0.4s ease-out forwards;
}

.animate-fade-in-up {
    animation: fadeInUp 0.6s var(--ease-out) forwards;
}

@keyframes fadeIn {
    from {
        opacity: 0;
    }

    to {
        opacity: 1;
    }
}

@keyframes fadeInUp {
    from {
        opacity: 0;
        transform: translateY(24px);
    }

    to {
        opacity: 1;
        transform: translateY(0);
    }
}

@keyframes shake {

    from,
    to {
        transform: translate3d(0, 0, 0);
    }

    10%,
    30%,
    50%,
    70%,
    90% {
        transform: translate3d(-10px, 0, 0);
    }

    20%,
    40%,
    60%,
    80% {
        transform: translate3d(10px, 0, 0);
    }
}

.shake {
    animation: shake 0.5s;
}



/* ── Hero Section ─────────────────────────────────────────────── */
.hero-caret {
    font-family: monospace;
    font-weight: 400;
    color: var(--accent-300);
    animation: caret-blink 0.8s step-end infinite;
}

@keyframes caret-blink {

    0%,
    100% {
        opacity: 1;
    }

    50% {
        opacity: 0;
    }
}



/* ── UI Design System (Preserving original colors/animations) ── */

/* The exact glass card style used across the site */
:root {
    --surface-card: rgba(255, 255, 255, 0.02);
    --surface-card-hover: rgba(255, 255, 255, 0.03);
    --surface-border: rgba(255, 255, 255, 0.08);
    --surface-border-hover: rgba(255, 255, 255, 0.10);
    --surface-shadow: 0 12px 28px rgba(0, 0, 0, 0.24);
    --surface-shadow-hover: 0 16px 34px rgba(0, 0, 0, 0.3);
    --control-radius: 0.875rem;

    /* ── Text ramp ──────────────────────────────────────────────────
       The site previously mixed Tailwind's `gray-*` (neutral) and
       `slate-*` (blue-tinted) families at the same lightness, which
       reads as a slightly muddy, inconsistent palette. These steps keep
       the exact same lightness as before but settle on one family, so
       text now agrees with the blue-leaning surfaces and cyan accent.
         --text-strong  was #f3f4f6 (gray-100)
         --text         was #e5e7eb (gray-200)
         --text-soft    was #d1d5db (gray-300)
         --text-muted   was #9ca3af (gray-400)                      */
    --text-strong: #f1f5f9;
    --text: #e2e8f0;
    --text-soft: #cbd5e1;
    --text-muted: #94a3b8;
    --text-faint: #64748b;
    --text-dim: #475569;

    /* ── Accent ramp (cyan — the brand colour) ───────────────────────
       Every cyan on the site was already one of these five values; they
       just lived as literals in a dozen files. Note there is no bare
       `--accent`: about.css scopes that name per profile card as a
       *brand* colour, and shadowing it here would be confusing. */
    --accent-100: #cffafe;
    --accent-200: #a5f3fc;
    --accent-300: #67e8f9;
    --accent-400: #22d3ee;
    --accent-500: #06b6d4;
    --accent-700: #0e7490;
    --accent-900: #164e63;
    /* Focus ring + the soft halo behind it, shared by every control */
    --accent-ring: rgba(34, 211, 238, 0.45);
    --accent-halo: rgba(34, 211, 238, 0.15);
    --accent-select: rgba(6, 182, 212, 0.3);

    /* Success / error, used by the copy-link button and editor pens */
    --ok: #4ade80;
    --danger: #f87171;

    /* Hairlines inside rich text (was gray-700 rgba(55, 65, 81, …)) */
    --rule: rgba(51, 65, 85, 0.5);

    /* ── Motion ─────────────────────────────────────────────────────
       Three named curves replace the same three cubic-beziers that were
       repeated ~30 times, so a tweak lands everywhere at once.
         --ease-out      settle / reveal (decelerating)
         --ease-spring   release (slight overshoot)
         --ease-standard press (symmetric)                          */
    --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
    --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
    --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);

    --dur-tap: 90ms;
    --dur-fast: 180ms;
    --dur: 220ms;
    --dur-slow: 300ms;
    --dur-release: 260ms;
}

/* ── Keyboard focus ────────────────────────────────────────────────
   Only `.search-inline-input` had a visible focus style, so every other
   control was unreachable-looking for keyboard users. `:focus-visible`
   means pointer users still never see a ring. `outline` is used rather
   than box-shadow so it cannot disturb layout or fight the glass
   box-shadows already on these elements. The ring automatically follows
   each element's own border-radius, so no radius is set here. */
:where(a, button, input, textarea, select, summary, [tabindex]):focus-visible {
    outline: 2px solid var(--accent-ring);
    outline-offset: 2px;
}

.glass-card {
    background: var(--surface-card);
    backdrop-filter: blur(16px);
    -webkit-backdrop-filter: blur(16px);
    border-radius: 1.125rem;
    box-shadow:
        var(--surface-shadow),
        inset 0 1px 0 rgba(255, 255, 255, 0.08);
    border: 1px solid var(--surface-border);
    transition:
        background var(--dur) ease,
        border-color var(--dur) ease,
        box-shadow var(--dur) ease,
        transform var(--dur-slow) var(--ease-spring);
}

.glass-card:hover {
    background: var(--surface-card-hover);
    border-color: var(--surface-border-hover);
    box-shadow:
        var(--surface-shadow-hover),
        inset 0 1px 0 rgba(255, 255, 255, 0.1);
}

/* Sleek Modern Dark Card (Perfectly integrated with the site) */
.solid-content-card {
    background: var(--surface-card) !important;
    border: 1px solid var(--surface-border) !important;
    border-radius: 1.125rem !important;
    box-shadow:
        var(--surface-shadow),
        inset 0 1px 0 rgba(255, 255, 255, 0.08) !important;
    transition:
        background var(--dur) ease,
        border-color var(--dur) ease,
        box-shadow var(--dur) ease,
        transform var(--dur-slow) var(--ease-spring) !important;
    -webkit-transform-style: flat;
    transform-style: flat;
    /* Fix Android Chrome: overflow:hidden + border-radius fails to clip
       during momentum/overscroll. contain:paint forces compositor-level
       clipping so content never bleeds outside rounded corners. */
    contain: paint;
}

.solid-content-card:hover {
    background: var(--surface-card-hover) !important;
    border-color: var(--surface-border-hover) !important;
    box-shadow:
        var(--surface-shadow-hover),
        inset 0 1px 0 rgba(255, 255, 255, 0.1) !important;
}

/* Unified Button System — Apple White Glass */
.btn-primary,
.btn-ghost,
.btn-pagination,
.btn-hero-primary,
.btn-hero-ghost {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    border-radius: var(--control-radius);
    line-height: 1;
    text-decoration: none;
    white-space: nowrap;
    transform: translate3d(0, 0, 0);
    backface-visibility: hidden;
    -webkit-backface-visibility: hidden;
    transition:
        background var(--dur-fast) ease,
        border-color var(--dur-fast) ease,
        color var(--dur-fast) ease,
        box-shadow var(--dur-fast) ease,
        transform var(--dur-release) var(--ease-spring);
    background: linear-gradient(180deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0.01) 100%);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    color: var(--text-strong);
    font-size: 0.875rem;
    font-weight: 600;
    border: 1px solid rgba(255, 255, 255, 0.08) !important;
    box-shadow:
        0 1px 2px rgba(0, 0, 0, 0.2),
        inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

.btn-primary:hover,
.btn-ghost:hover,
.btn-pagination:hover,
.btn-hero-primary:hover,
.btn-hero-ghost:hover {
    background: linear-gradient(180deg, rgba(255, 255, 255, 0.08) 0%, rgba(255, 255, 255, 0.03) 100%);
    border-color: rgba(255, 255, 255, 0.12) !important;
    color: #ffffff;
    box-shadow:
        0 2px 4px rgba(0, 0, 0, 0.2),
        inset 0 1px 0 rgba(255, 255, 255, 0.1);
}

.btn-primary,
.btn-ghost,
.btn-pagination,
.btn-hero-primary,
.btn-hero-ghost {
    -webkit-tap-highlight-color: transparent;
}

/* Project technology chips — matches footer category pill style */
.tech-chip {
    display: inline-flex;
    align-items: center;
    padding: 0.25rem 0.75rem;
    border-radius: 9999px;
    font-size: 0.6875rem;
    font-weight: 500;
    letter-spacing: 0.02em;
    color: var(--text-soft);
    background: rgba(255, 255, 255, 0.06);
    border: 1px solid rgba(255, 255, 255, 0.12);
}

@media (min-width: 768px) {
    .tech-chip {
        font-size: 0.75rem;
    }
}

/* Layout Helpers */
/* Home "latest posts" grid — flexbox so a lone 3rd card centres on its
   own row at the tablet (2-up) breakpoint instead of hanging left. */
.blog-cards-grid {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 1.5rem;
    max-width: 365px;
    margin-left: auto;
    margin-right: auto;
}

.line-clamp-2,
.line-clamp-3 {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.line-clamp-2 {
    -webkit-line-clamp: 2;
}

.line-clamp-3 {
    -webkit-line-clamp: 3;
}

/* Project description with inline "more / less" toggle.
   JS truncates by words so the button sits at the end of the 3rd line and the
   author's own line breaks are preserved. */
.project-desc {
    white-space: pre-line;
    margin-bottom: 1rem;
}

/* Pre-JS / no-JS fallback: clamp to 3 lines (button added later by JS). */
.project-desc.is-clamped {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    overflow: hidden;
}

.project-desc-toggle {
    background: none;
    border: none;
    color: var(--text-faint);
    font: inherit;
    /* Match the mobile body size (text-sm) so the inline button is never
       TALLER than the text line. A taller inline-block would grow the line
       box and make the text jump vertically when toggling on mobile. */
    font-size: 0.875rem;
    line-height: 1.625;
    vertical-align: baseline;
    font-weight: 500;
    padding: 0;
    margin: 0;
    cursor: pointer;
    transition: color 0.2s;
    white-space: nowrap;
}

/* "less" button — small gap so it doesn't touch the final word. */
.project-desc-toggle--gap {
    margin-left: 0.4rem;
}

.project-desc-toggle:hover {
    color: var(--text-muted);
    text-decoration: underline;
}

.project-desc-ellipsis {
    color: inherit;
}

.hero-typed-row {
    min-height: 1.5rem;
}

.stat-label {
    font-size: 0.75rem;
    letter-spacing: 0.08em;
}

.search-inline-input {
    width: 100%;
    background: rgba(15, 23, 42, 0.6);
    border: 1px solid rgba(148, 163, 184, 0.2);
    color: var(--text);
    border-radius: 0.75rem;
    padding: 0.75rem 1rem;
    outline: none;
    transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.search-inline-input:focus {
    border-color: var(--accent-ring);
    box-shadow: 0 0 0 3px var(--accent-halo);
}

/* The input paints its own accent halo, so suppress the global outline
   here — otherwise a keyboard focus draws two rings on top of each other. */
.search-inline-input:focus-visible {
    outline: none;
}

.topic-count {
    font-size: 0.8rem;
    color: var(--text-muted);
}

/* ── Topics grid cards ──────────────────────────────────────────
   Icon-led, left-aligned list cards that match the site's blog/
   project cards (tag icon + title + meta) instead of plain centred
   text boxes. Hover stays subtle: colour shift + a small arrow nudge,
   layered on top of the shared .glass-card hover. */
.topic-card {
    display: flex;
    align-items: center;
    gap: 0.875rem;
    padding: 1rem 1.125rem;
    text-decoration: none;
    height: 100%;
    overflow: hidden;
}

.topic-card-body {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    flex: 1;
    min-width: 0;
}

.topic-card-name {
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--text);
    line-height: 1.3;
    overflow-wrap: anywhere;
}

.topic-card-arrow {
    flex-shrink: 0;
    font-size: 0.95rem;
    color: var(--text-faint);
}

/* Press feedback — quick scale-down on click/tap. !important is required
   to override the .reveal/.reveal-visible transform (which otherwise wins
   by source order on desktop and via !important on mobile). */
.topic-card:active {
    transform: scale(0.96) !important;
    transition: transform var(--dur-tap) var(--ease-standard) !important;
}

@media (min-width: 640px) {
    .blog-cards-grid {
        max-width: 760px;
    }
}

@media (min-width: 1024px) {
    .blog-cards-grid {
        max-width: 1250px;
    }
}

.blog-card-item {
    min-width: 0;
    word-break: break-word;
    overflow-wrap: anywhere;
    padding: 1rem;
    /* Mobile: single column, full width */
    width: 100%;
}

/* Tablet: two cards per row */
@media (min-width: 640px) {
    .blog-card-item {
        width: calc(50% - 0.75rem);
    }
}

/* Desktop: three cards per row */
@media (min-width: 1024px) {
    .blog-card-item {
        width: calc(33.333% - 1rem);
        padding: 1.25rem;
    }
}

/* Blog List Card Thumbnail */
.blog-list-thumb {
    width: 100%;
    height: auto;
    aspect-ratio: 16 / 9;
    overflow: hidden;
    border-radius: 0.75rem;
    flex-shrink: 0;
}

.blog-list-card {
    padding: 1rem;
}

@media (min-width: 640px) {
    .blog-list-card {
        flex-direction: row;
        /* Keep image-less cards the same height as cards with a thumbnail */
        min-height: calc(169px + 2rem);
    }

    .blog-list-thumb {
        width: 300px;
        min-width: 300px;
        height: 169px;
        flex-shrink: 0;
    }
}

@media (min-width: 768px) {
    .blog-list-card {
        padding: 1.25rem;
        min-height: calc(200px + 2.5rem);
    }

    .blog-list-thumb {
        width: 356px;
        min-width: 356px;
        height: 200px;
    }
}

/* Content list container */
.list-container {
    padding: 1.25rem;
    color: white;
    max-width: 1100px;
    margin-left: auto;
    margin-right: auto;
    isolation: isolate;
}

@media (min-width: 768px) {
    .list-container {
        padding: 2rem 2.5rem;
    }
}

/* Blog Card & Blogpost Meta Line — stack on mobile */
.post-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.125rem 0.25rem;
    color: var(--text-muted);
    font-size: 0.875rem;
    margin-top: 0.5rem;
    margin-bottom: 1rem;
}

/* Pagination Button (Apple White Glass) */
.btn-pagination {
    width: 9rem;
    padding: 0.625rem 1rem;
    justify-content: center;
}

/* Blogpost Thumbnail Container */
.blogpost-thumbnail-wrap {
    margin: 1.5rem auto;
    max-width: 48rem;
    width: 100%;
    display: flex;
    justify-content: center;
}

.blogpost-thumbnail-wrap img {
    max-width: 100%;
    max-height: 450px;
    width: auto;
    height: auto;
    display: block;
    border-radius: 0.75rem;
}

/* Primary Buttons (Hero and General) — stands out with subtle blueness */
.btn-hero-primary {
    padding: 0.875rem 1.25rem;
}

.btn-hero-primary,
.btn-cyan {
    background: linear-gradient(180deg, rgba(6, 182, 212, 0.12) 0%, rgba(6, 182, 212, 0.03) 100%);
    border-color: rgba(6, 182, 212, 0.2) !important;
    color: var(--accent-100);
    box-shadow:
        0 1px 2px rgba(0, 0, 0, 0.2),
        inset 0 1px 0 rgba(6, 182, 212, 0.15);
}

.btn-hero-primary:hover,
.btn-cyan:hover {
    background: linear-gradient(180deg, rgba(6, 182, 212, 0.18) 0%, rgba(6, 182, 212, 0.06) 100%);
    border-color: rgba(6, 182, 212, 0.35) !important;
    color: #ffffff;
    box-shadow:
        0 2px 4px rgba(0, 0, 0, 0.2),
        inset 0 1px 0 rgba(6, 182, 212, 0.25);
}

.btn-hero-ghost {
    padding: 0.875rem 1.25rem;
}

/* ── Mobile Touch Enhancements ────────────────────────────────── */
@media (hover: none) and (pointer: coarse) {

    /* Solid content cards should NOT go transparent on tap (grid bleeds through) */
    .solid-content-card:active,
    .solid-content-card.tapped {
        opacity: 1 !important;
        transform: scale(0.985) !important;
    }

    /* Prevent text links from scaling, just change opacity */
    p a:active,
    span a:active,
    .blog-content a:active,
    .post-meta a:active,
    footer a:not(.btn-icon):active {
        transform: none !important;
        opacity: 0.5 !important;
    }



    /* Kill backdrop-filter on glass cards for mobile.  backdrop-filter +
       border-radius causes GPU seams / broken corners on weaker Android
       devices during momentum scroll.  The blur is invisible on a solid
       dark background anyway, so removing it costs nothing visually.
       The mask forces the compositor to clip rounded corners perfectly. */
    .glass-card {
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
        -webkit-mask-image: -webkit-radial-gradient(white, black);
        mask-image: linear-gradient(#000, #000);
    }

    /* Skip rendering of off-screen list cards — the browser reserves
       their height so the scrollbar stays accurate, but avoids painting
       cards the user can't see yet (biggest scroll-smoothness win). */
    .blog-list-card {
        content-visibility: auto;
        contain-intrinsic-size: auto 360px;
    }

    /* Small UI elements keep blur — too few pixels to trigger GPU issues */
    .btn-hero-ghost,
    #back-to-top {
        backdrop-filter: blur(8px);
        -webkit-backdrop-filter: blur(8px);
    }

    .btn-primary,
    .btn-ghost,
    .btn-icon,
    .btn-pagination,
    .btn-hero-primary,
    .btn-hero-ghost,
    #back-to-top {
        transition-duration: var(--dur-fast);
    }
}

/* bfcache :hover reset — momentarily kill pointer-events (added/removed by JS) */
.no-hover-reset {
    pointer-events: none !important;
}

/* JS-driven tap feedback (.tapped class added/removed by base.js) */
.tapped {
    transform: scale(0.93) translateZ(0) !important;
    opacity: 0.8 !important;
    transition:
        transform var(--dur-tap) var(--ease-standard),
        opacity var(--dur-tap) var(--ease-standard) !important;
}

/* Search term highlighting (blog_extras.highlight) — cyan accent that
   matches the site theme instead of the browser-default yellow. */
mark {
    background: rgba(6, 182, 212, 0.22);
    color: #a5f3fc;
    border-radius: 3px;
    padding: 0 0.15em;
}

/* Text links inside paragraphs: no scale, just opacity */
p a.tapped,
span a.tapped,
.blog-content a.tapped,
.post-meta a.tapped {
    transform: none !important;
    opacity: 0.5 !important;
}

.btn-icon.tapped {
    transform: scale(0.85) translateZ(0) !important;
}

.solid-content-card.tapped {
    opacity: 1 !important;
    transform: scale(0.985) !important;
}

/* Nav & timeline elements: no tap shrink */
.nav-btn.tapped,
.searchBtn.tapped,
.searchBtn1.tapped,
.timeline-header-link.tapped,
.timeline-card-header.tapped,
.timeline-role-content.tapped {
    transform: none !important;
    opacity: 1 !important;
}

/* Lock page scroll while the fullscreen search modal is open — the
   image lightbox already does this via body.image-lightbox-open.
   The mobile menu deliberately does NOT lock scroll: it closes itself
   when the user scrolls (handled in base.js). */
body.search-open {
    overflow: hidden;
}

@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;
    }
}

/* ── Scroll Reveal Animations ────────────────────────────────── */
/* Elements with .reveal start invisible and animate in via IntersectionObserver */
.reveal {
    opacity: 0;
    transform: translateY(24px);
    transition:
        opacity 0.6s var(--ease-out),
        transform 0.6s var(--ease-out);
    /* Removed will-change to prevent mobile GPU VRAM bloat on large lists */
}

/* Mobile: Remove the heavy translateY animation. A simple fade-in is much 
   lighter on the mobile GPU and eliminates jank while scrolling. */
@media (max-width: 768px) {
    .reveal {
        transform: none !important;
        transition: opacity 0.5s ease-out;
    }
}

.reveal.reveal-visible {
    opacity: 1;
    transform: translateY(0);
    will-change: auto;
}

/* Variant: fade in without vertical movement */
.reveal-fade {
    opacity: 0;
    transition: opacity 0.6s var(--ease-out);
    will-change: opacity;
}

.reveal-fade.reveal-visible {
    opacity: 1;
    will-change: auto;
}

/* Variant: scale up */
.reveal-scale {
    opacity: 0;
    transform: scale(0.92);
    transition:
        opacity 0.5s var(--ease-out),
        transform 0.5s var(--ease-out);
}

.reveal-scale.reveal-visible {
    opacity: 1;
    transform: scale(1);
    will-change: auto;
}

/* Variant: slide from left */
.reveal-left {
    opacity: 0;
    transform: translateX(-20px);
    transition:
        opacity 0.5s var(--ease-out),
        transform 0.5s var(--ease-out);
}

.reveal-left.reveal-visible {
    opacity: 1;
    transform: translateX(0);
    will-change: auto;
}

/* Stagger children: each child delays slightly */
.reveal-stagger>.reveal {
    transition-delay: calc(var(--reveal-i, 0) * 0.08s);
}

.reveal-stagger>.reveal-scale {
    transition-delay: calc(var(--reveal-i, 0) * 0.06s);
}

/* Override for cards with !important transforms — use opacity-only reveal */
.solid-content-card.reveal {
    opacity: 0 !important;
    transform: translateY(12px) !important;
    transition:
        opacity 0.6s var(--ease-out),
        transform 0.6s var(--ease-out),
        background 0.3s ease,
        box-shadow 0.3s ease !important;
}

.solid-content-card.reveal.reveal-visible {
    opacity: 1 !important;
    transform: none !important;
}

.solid-content-card.reveal.reveal-visible:hover {
    transform: none !important;
}

/* ── Shared Rich Content Styles (.blog-content) ─────────────────
   Loaded on every page via base.html so that both the About bio
   and individual blog posts render CKEditor content identically.
   Page-specific overrides live in about.css and blogpost.css.
   ─────────────────────────────────────────────────────────────── */
.blog-content {
    overflow-wrap: anywhere;
    word-break: break-word;
    min-width: 0;
    overflow-x: hidden;
}

/* Restore browser heading hierarchy inside rich-text areas.
   Scoped to .blog-content so page-level headings are unaffected. */
.blog-content h1,
.blog-content h2,
.blog-content h3,
.blog-content h4,
.blog-content h5,
.blog-content h6 {
    font-size: revert;
    font-weight: revert;
}

.blog-content h1,
.blog-content h2,
.blog-content h3,
.blog-content h4,
.blog-content h5,
.blog-content h6 {
    color: var(--text-strong);
    margin-top: 1.5rem;
    margin-bottom: 0.75rem;
    font-weight: 600;
    line-height: 1.3;
    letter-spacing: 0.025em;
    /* TOC anchor links jump here — keep headings clear of the sticky nav */
    scroll-margin-top: 6rem;
}

.blog-content h1 {
    font-size: 1.75rem;
    margin-top: 2rem;
}

@media (min-width: 768px) {
    .blog-content h1 {
        font-size: 2.25rem;
    }
}

.blog-content h2 {
    font-size: 1.5rem;
    border-bottom: 1px solid var(--rule);
    padding-bottom: 0.5rem;
}

@media (min-width: 768px) {
    .blog-content h2 {
        font-size: 1.875rem;
    }
}

.blog-content h3 {
    font-size: 1.25rem;
}

@media (min-width: 768px) {
    .blog-content h3 {
        font-size: 1.5rem;
    }
}

.blog-content p {
    margin-bottom: 1rem;
    line-height: 1.65;
    color: var(--text-strong);
    font-size: 1.05rem;
    overflow-wrap: anywhere;
    word-break: break-word;
}

.blog-content ul,
.blog-content ol {
    margin-top: 0.75rem;
    margin-bottom: 1.25rem;
    padding-left: 2.25rem; /* Generous padding so multi-digit numbers, letters, and Roman numerals (VIII.) never get clipped */
    color: var(--text-strong);
    line-height: 1.75;
    font-size: 1.05rem;
}

.blog-content ul:not(.todo-list):not(.contains-task-list):not(.task-list):not(.tk-checklist__list):not(.tk-quiz__options) {
    list-style-type: disc;
}

.blog-content .todo-list,
.blog-content .contains-task-list,
.blog-content .task-list,
.blog-content .tk-checklist__list,
.blog-content .tk-quiz__options,
.blog-content .tk-quiz ol,
.blog-content .tk-quiz ul {
    list-style: none !important;
    list-style-type: none !important;
    padding-left: 0 !important;
    margin-left: 0 !important;
}

.blog-content .todo-list li,
.blog-content .contains-task-list li,
.blog-content .task-list li,
.blog-content .tk-checklist__item,
.blog-content .tk-quiz__option,
.blog-content .tk-quiz li {
    list-style: none !important;
    list-style-type: none !important;
}

.blog-content .todo-list li::marker,
.blog-content .todo-list li::before,
.blog-content .contains-task-list li::marker,
.blog-content .contains-task-list li::before,
.blog-content .task-list li::marker,
.blog-content .task-list li::before,
.blog-content .tk-checklist__item::marker,
.blog-content .tk-checklist__item::before,
.blog-content .tk-quiz__option::marker,
.blog-content .tk-quiz__option::before,
.blog-content .tk-quiz li::marker,
.blog-content .tk-quiz li::before {
    display: none !important;
    content: "" !important;
}

.blog-content ol {
    list-style-type: decimal;
}

/* HTML5 type attribute support for CKEditor 5 */
.blog-content ol[type="1"] { list-style-type: decimal; }
.blog-content ol[type="01"] { list-style-type: decimal-leading-zero; }
.blog-content ol[type="a"] { list-style-type: lower-alpha; }
.blog-content ol[type="A"] { list-style-type: upper-alpha; }
.blog-content ol[type="i"] { list-style-type: lower-roman; }
.blog-content ol[type="I"] { list-style-type: upper-roman; }

/* CKEditor inline style overrides */
.blog-content ol[style*="decimal-leading-zero"], .blog-content li[style*="decimal-leading-zero"] { list-style-type: decimal-leading-zero !important; }
.blog-content ol[style*="lower-alpha"], .blog-content li[style*="lower-alpha"] { list-style-type: lower-alpha !important; }
.blog-content ol[style*="upper-alpha"], .blog-content li[style*="upper-alpha"] { list-style-type: upper-alpha !important; }
.blog-content ol[style*="lower-roman"], .blog-content li[style*="lower-roman"] { list-style-type: lower-roman !important; }
.blog-content ol[style*="upper-roman"], .blog-content li[style*="upper-roman"] { list-style-type: upper-roman !important; }
.blog-content ol[style*="decimal"]:not([style*="decimal-leading-zero"]), .blog-content li[style*="decimal"]:not([style*="decimal-leading-zero"]) { list-style-type: decimal !important; }
.blog-content ul[style*="circle"], .blog-content li[style*="circle"] { list-style-type: circle !important; }
.blog-content ul[style*="square"], .blog-content li[style*="square"] { list-style-type: square !important; }

/* Nested lists: clean indentation hierarchy */
.blog-content ul ul,
.blog-content ol ol,
.blog-content ul ol,
.blog-content ol ul {
    margin-top: 0.35rem;
    margin-bottom: 0.35rem;
    padding-left: 1.75rem;
}

.blog-content ul ul { list-style-type: circle; }
.blog-content ul ul ul { list-style-type: square; }
.blog-content ol ol { list-style-type: lower-alpha; }
.blog-content ol ol ol { list-style-type: lower-roman; }

.blog-content li {
    margin-bottom: 0.4rem;
    padding-left: 0.25rem;
}

.blog-content blockquote {
    background: rgba(255, 255, 255, 0.03);
    backdrop-filter: blur(8px);
    border-left: 4px solid var(--accent-500);
    padding: 0.75rem 1.25rem;
    margin: 1.25rem 0;
    border-radius: 0 0.75rem 0.75rem 0;
    color: var(--text-muted);
    font-style: italic;
    box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}

.blog-content blockquote p {
    margin-bottom: 0.5rem;
}

.blog-content blockquote p:last-child {
    margin-bottom: 0;
}

/* CKEditor font-size utility classes — canonical values */
.blog-content .text-tiny {
    font-size: 0.75em;
    color: inherit;
}

.blog-content .text-small {
    font-size: 0.875em;
    color: inherit;
}

.blog-content .text-big {
    font-size: 1.25em;
    color: inherit;
}

.blog-content .text-huge {
    font-size: 1.5em;
    color: inherit;
}

.blog-content a {
    color: var(--accent-300);
    text-decoration: none !important;
    background-image: linear-gradient(currentColor, currentColor);
    background-repeat: no-repeat;
    background-size: 0 2px;
    background-position: 0 100%;
    transition:
        color var(--dur) ease,
        background-size var(--dur) ease;
    overflow-wrap: anywhere;
    word-break: break-all;
}

.blog-content a:hover,
.blog-content a:focus {
    color: var(--accent-200);
    background-size: 100% 2px;
}

.blog-content img,
.blog-content figure img,
p iframe {
    border-radius: 8px;
}

.blog-content img {
    max-width: 100%;
    height: auto;
}

.blog-content figure.image {
    display: flex;
    flex-direction: column;
    width: fit-content;
    margin: 1.5rem auto;
    max-width: 100%;
    border-radius: 8px;
    overflow: hidden;
}

.blog-content figure.image img {
    margin: 0;
    display: block;
    border-radius: 0 !important;
}

/* Images resized via the editor handles carry an inline width on the
   <figure>; make the inner <img> fill that width so the resize sticks. */
.blog-content figure.image.image_resized {
    box-sizing: border-box;
    display: block;
}

.blog-content figure.image.image_resized img {
    width: 100%;
}

.blog-content figure.image figcaption {
    padding: 0.3rem 0.75rem;
    text-align: center;
    font-size: 0.85em;
    color: var(--text);
    background: rgba(51, 65, 85, 0.8);
}

/* ── Image alignment (CKEditor imageStyle: alignLeft/Center/Right) ──
   These classes are emitted by the editor but only styled inside the
   admin bundle. Re-declare them so alignment works on the public site. */
.blog-content figure.image.image-style-align-center {
    margin-left: auto;
    margin-right: auto;
}

.blog-content figure.image.image-style-align-left {
    float: left;
    max-width: 50%;
    margin: 0.5rem 1.5rem 1rem 0;
}

.blog-content figure.image.image-style-align-right {
    float: right;
    max-width: 50%;
    margin: 0.5rem 0 1rem 1.5rem;
}

/* On narrow screens floated images become full-width blocks so text
   never gets squeezed into an unreadable column beside them. */
@media (max-width: 640px) {
    .blog-content figure.image.image-style-align-left,
    .blog-content figure.image.image-style-align-right {
        float: none;
        max-width: 100%;
        margin: 1.5rem auto;
    }
}

/* ── Highlight markers & pens (CKEditor highlight feature) ──────────
   Background markers keep dark text for contrast on the bright fills;
   pen colours are brightened for legibility on the dark theme. */
.blog-content .marker-yellow,
.blog-content .marker-green,
.blog-content .marker-pink,
.blog-content .marker-blue {
    color: #0f172a;
    padding: 0 0.1em;
    border-radius: 2px;
}

.blog-content .marker-yellow {
    background-color: #fdfd77;
}

.blog-content .marker-green {
    background-color: #62f962;
}

.blog-content .marker-pink {
    background-color: #fc7899;
}

.blog-content .marker-blue {
    background-color: #72ccfd;
}

.blog-content .pen-red {
    background-color: transparent;
    color: var(--danger);
}

.blog-content .pen-green {
    background-color: transparent;
    color: var(--ok);
}

/* ── Rich-content embeds ──────────────────────────────────────────────
   Shared by every page that renders editor output (.blog-content):
   blog posts and the About bio. These used to live in blogpost.css, so
   an embed in the About bio was left with the 1rem margin below — which
   on CKEditor's absolutely positioned iframe shifts it out of its
   aspect-ratio box and over the following paragraph. */
.blog-content figure {
    margin: 1.5rem 0;
}

.blog-content iframe {
    width: 100%;
    max-width: 100%;
    margin: 1rem 0;
    border: none;
    border-radius: 14px;
}

/* Bare iframes pasted into the editor get their own sizing */
.blog-content iframe[src*="youtube.com"],
.blog-content iframe[src*="youtu.be"] {
    aspect-ratio: 16 / 9;
    height: auto;
}

.blog-content iframe[src*="spotify.com"] {
    height: 152px;
    border-radius: 14px;
    background-color: transparent;
}

/* CKEditor instead wraps embeds in a relative box that reserves the
   space via padding-bottom, and pins the iframe to its corners. There
   the wrapper owns the geometry: a margin would push the iframe out of
   the reserved space and over the next paragraph, and an aspect-ratio
   or fixed height would fight the wrapper. */
.blog-content figure.media iframe,
.blog-content [data-oembed-url] iframe {
    margin: 0;
    aspect-ratio: auto;
    height: 100%;
}

/* ── Spotify embeds ──────────────────────────────────────────────
   Spotify players are fixed-height widgets, NOT 16:9 videos: a track
   is 152px, an artist 352px, a playlist/album 380px, a show/episode
   232px (the sizes Spotify's own embed generator emits).
   CKEditor's generic media wrapper above reserves a 16:9 box, which
   stretches a single track to 56.25% of the width and leaves a huge
   empty gap below the widget. Undo that box for Spotify embeds and
   pin the iframe to the height Spotify actually renders, so corners
   hug the widget instead of floating in white space. */
.blog-content iframe[src*="spotify.com"][src*="/embed/artist/"],
.blog-content iframe[src*="spotify.com"][src*="/embed/playlist/"],
.blog-content iframe[src*="spotify.com"][src*="/embed/album/"] {
    height: 450px;
}

.blog-content iframe[src*="spotify.com"][src*="/embed/show/"],
.blog-content iframe[src*="spotify.com"][src*="/embed/episode/"] {
    height: 232px;
}

/* Collapse the editor's aspect-ratio box for Spotify embeds. The
   padding div is an immediate child of [data-oembed-url]; making it
   static/auto lets the fixed-height iframe define the box. */
.blog-content [data-oembed-url*="spotify.com"] > div {
    position: static !important;
    height: auto !important;
    padding-bottom: 0 !important;
}

.blog-content [data-oembed-url*="spotify.com"] iframe {
    position: static !important;
    display: block;
    width: 100%;
    height: 152px !important;
    margin: 0;
    border-radius: 14px;
    background-color: transparent;
}

.blog-content [data-oembed-url*="spotify.com"] iframe[src*="/embed/artist/"],
.blog-content [data-oembed-url*="spotify.com"] iframe[src*="/embed/playlist/"],
.blog-content [data-oembed-url*="spotify.com"] iframe[src*="/embed/album/"] {
    height: 450px !important;
}

.blog-content [data-oembed-url*="spotify.com"] iframe[src*="/embed/show/"],
.blog-content [data-oembed-url*="spotify.com"] iframe[src*="/embed/episode/"] {
    height: 232px !important;
}

.video-wrapper {
    position: relative;
    width: 100%;
    padding-bottom: 56.25%;
    height: 0;
    overflow: hidden;
    margin: 1rem 0;
}

.video-wrapper iframe {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    border: none;
    border-radius: 8px;
}

.blog-content code:not(pre code) {
    background-color: rgba(71, 85, 105, 0.2) !important;
    color: var(--text-strong) !important;
    padding: 0.2em 0.4em !important;
    border-radius: 4px !important;
    font-size: 0.9em !important;
    font-family:
        ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
        "Liberation Mono", "Courier New", monospace !important;
    border: 1px solid rgba(148, 163, 184, 0.15) !important;
    word-break: break-word !important;
}

/* ── Tables ─────────────────────────────────────────────────────── */
/* CKEditor 5 wraps tables in <figure class="table">, and CKEditor's own
   content CSS gives that figure display: table — so it grows to the
   table's intrinsic width and, with .blog-content's overflow-x: hidden,
   wide tables get clipped with no way to reach the rest. Keep the table
   a real table (border-collapse needs table display) and make the
   wrapper scroll horizontally instead. Bare tables pasted in as HTML
   get the same treatment via a .table-wrap div added by base.js. */
.blog-content figure.table {
    display: block;
    max-width: 100%;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

.blog-content .table-wrap {
    max-width: 100%;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    margin: 1.5rem 0;
}

.blog-content table {
    display: table;
    width: auto;
    min-width: 100%;
    border-collapse: collapse;
    margin: 1.5rem 0;
    font-size: 0.95rem;
}

.blog-content figure.table table,
.blog-content .table-wrap table {
    margin: 0;
}

.blog-content table th,
.blog-content table td {
    padding: 0.65rem 0.9rem;
    border: 1px solid rgba(255, 255, 255, 0.1);
    text-align: left;
    color: var(--text-strong);
}

.blog-content table th {
    background: rgba(6, 182, 212, 0.1);
    color: var(--text);
    font-weight: 600;
    white-space: nowrap;
}

.blog-content table tr:hover td {
    background: rgba(255, 255, 255, 0.03);
}

/* ── Horizontal Rule ─────────────────────────────────────────────── */
.blog-content hr {
    border: none;
    height: 1px;
    background: var(--rule);
    margin: 1.5rem 0;
}

/* Compact article toolbar: visible early without competing with the content. */
.share-section {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    margin-top: 1.5rem;
    padding: 0.75rem 0;
    border-bottom: 1px solid var(--rule);
}

.share-section__label {
    margin: 0;
    color: var(--text-muted);
    /* Matches .post-meta so the label reads at the same size as the
       "May 6, 2026 · 33 min read" line above it. */
    font-size: 0.875rem;
    font-weight: 500;
    white-space: nowrap;
}

.share-section__actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 0.5rem;
}

.share-section .btn-icon {
    width: 2.25rem;
    height: 2.25rem;
    font-size: 1rem;
    flex-shrink: 0;
}

.share-section .btn-icon__glyph {
    width: 1rem;
    height: 1rem;
}

@media (max-width: 360px) {
    .share-section {
        flex-wrap: wrap;
        gap: 0.625rem;
    }

    .share-section__actions {
        gap: 0.375rem;
    }
}

@media (max-width: 320px) {
    .share-section {
        align-items: flex-start;
        flex-direction: column;
    }

    .share-section__actions {
        justify-content: flex-start;
    }
}


/* ── Interactive Micro-Animations ────────────────────────────── */

/* ── Social icon rows ───────────────────────────────────────────── */
/* Up to seven brand icons (GitHub, LinkedIn, Telegram, LeetCode, X,
   YouTube, Instagram) don't fit on one line on narrow phones. Wrap
   onto extra lines instead of overflowing the viewport. */
.social-row {
    flex-wrap: wrap;
}

/* 4-column social dock on About page header — guarantees 4 items per row */
.social-grid {
    display: grid !important;
    grid-template-columns: repeat(4, 2.75rem) !important;
    gap: 0.75rem !important;
    width: max-content !important;
    margin-left: auto;
    margin-right: auto;
}

@media (min-width: 640px) {
    .social-grid {
        margin-left: 0;
        margin-right: 0;
    }
}

/* Controls should never show the long-press callout menu or accidentally
   select their own text on tap — both feel janky on touch devices.
   (Content text stays fully selectable; this targets controls only.) */
.btn-primary,
.btn-ghost,
.btn-icon,
.btn-pagination,
.btn-hero-primary,
.btn-hero-ghost,
.nav-btn,
.searchBtn,
.searchBtn1,
.searchCloseBtn,
.searchSubmitBtn,
.mob-nav a,
#back-to-top,
.project-desc-toggle {
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
}

/* Button Click/Active State
   Press snaps down fast (90ms); the spring transform on each element's
   resting transition handles a subtle pop back up on release. */
.btn-primary:active,
.btn-ghost:active,
.btn-icon:active,
.btn-pagination:active,
.btn-hero-primary:active,
.btn-hero-ghost:active,
.nav-btn:active,
.searchBtn:active,
.searchBtn1:active,
.searchCloseBtn:active,
.searchSubmitBtn:active,
nav a:not(.btn-hero-primary):active,
.mob-nav a:active,
#back-to-top:active,
.project-desc-toggle:active {
    transform: scale(0.94) translateZ(0) !important;
    transition: transform var(--dur-tap) var(--ease-standard) !important;
}

/* Card Click/Active State — gentler dip, springy release */
.glass-card:active,
.blog-list-card:active {
    transform: scale(0.975) translateZ(0) !important;
    transition: transform var(--dur-tap) var(--ease-standard) !important;
}

/* ── Size modifiers — the single source of truth for height ──────
   Pair a size with any text-button variant, e.g.
   <a class="btn-primary btn-md"> … </a> */
.btn-sm {
    padding: 0.5rem 1rem;
    font-size: 0.8125rem;
}

.btn-md {
    padding: 0.625rem 1.25rem;
    font-size: 0.875rem;
}

.btn-lg {
    padding: 0.8125rem 1.5rem;
    font-size: 0.9375rem;
}

/* Hero buttons keep their generous default sizing */
.btn-hero-primary,
.btn-hero-ghost {
    padding: 0.875rem 1.5rem;
    font-size: 0.9375rem;
}

/* ── Icon button — one class for ALL round/square icon actions
   (footer socials, blogpost share, copy-link …).

   Behaves exactly like the hero buttons: transparent glass at rest
   (cf. "Resume"), then on hover the INSIDE fills with its own brand
   colour in a liquid-glass domed style (cf. "About me"). The glow is
   fully contained inside the glass — a vertical dome gradient, a soft
   radial bloom from the centre, and a top rim highlight. NO outer glow,
   NO lift, NO dark drop shadow. Each brand is just two custom
   properties (`--brand` fill, `--brand-glyph` icon). ──── */
.btn-icon {
    /* r,g,b channels so we can compose any opacity we need */
    --brand: 255, 255, 255;
    --brand-glyph: 255, 255, 255;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.75rem;
    height: 2.75rem;
    flex-shrink: 0;
    font-size: 1.25rem;
    border-radius: var(--control-radius);
    color: var(--text);
    text-decoration: none;
    background: linear-gradient(180deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0.01) 100%);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border: 1px solid rgba(255, 255, 255, 0.08);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
    -webkit-tap-highlight-color: transparent;
    transition:
        background var(--dur-slow) var(--ease-out),
        border-color var(--dur-slow) var(--ease-out),
        color var(--dur-slow) var(--ease-out),
        box-shadow var(--dur-slow) var(--ease-out),
        transform var(--dur-release) var(--ease-spring);
}

/* Brand SVG glyphs (X, LeetCode) rendered as masks so they inherit the
   button's `color` — at rest they read light grey like the icon-font
   glyphs, on hover they tint to the brand colour with everything else. */
.btn-icon__glyph {
    width: 1.25rem;
    height: 1.25rem;
    background-color: currentColor;
    -webkit-mask: var(--glyph) center / contain no-repeat;
    mask: var(--glyph) center / contain no-repeat;
}

/* Per-brand colours (resting glyph stays neutral via the base `color`). */
.btn-icon--github {
    --brand: 240, 246, 252;
    --brand-glyph: 245, 248, 252;
}

.btn-icon--linkedin {
    --brand: 20, 120, 220;
    --brand-glyph: 130, 185, 250;
}

.btn-icon--telegram {
    --brand: 38, 158, 215;
    --brand-glyph: 130, 200, 240;
}

.btn-icon--x {
    --brand: 225, 228, 235;
    --brand-glyph: 240, 242, 248;
}

.btn-icon--leetcode {
    --brand: 245, 158, 30;
    --brand-glyph: 250, 195, 120;
}

.btn-icon--youtube {
    --brand: 255, 50, 50;
    --brand-glyph: 255, 130, 130;
}

.btn-icon--instagram {
    --brand: 225, 48, 108;
    --brand-glyph: 245, 130, 175;
}

.btn-icon--discord {
    --brand: 88, 101, 242;
    --brand-glyph: 160, 175, 255;
}

.btn-icon--globe {
    --brand: 6, 182, 212;
    --brand-glyph: 103, 232, 249;
}

.btn-icon--neutral {
    --brand: 210, 222, 240;
    --brand-glyph: 240, 244, 250;
}

/* Liquid-glass inner illumination on hover — light blooms softly from
   inside, fully contained within the button (Apple Control Center feel:
   even fill, clean rim, no harsh top seam). Scoped to real hover devices
   so touch never gets a stuck glow. */
@media (hover: hover) {
    .btn-icon:hover {
        background:
            radial-gradient(120% 120% at 50% 55%, rgba(var(--brand), 0.20) 0%, rgba(var(--brand), 0.05) 60%, rgba(var(--brand), 0) 100%),
            linear-gradient(180deg, rgba(var(--brand), 0.10) 0%, rgba(var(--brand), 0.03) 100%);
        border-color: rgba(var(--brand), 0.30);
        color: rgb(var(--brand-glyph));
        box-shadow:
            inset 0 1px 0 rgba(255, 255, 255, 0.10),
            inset 0 0 16px rgba(var(--brand), 0.10);
    }
}

/* ── Equal-width helper — for paired action buttons that should
   line up (e.g. GitHub / Live site on a project card). ─────────── */
.btn-uniform {
    width: 7rem;
    text-align: center;
    justify-content: center;
}

/* ── Full-width action pair helper — used on 404 / empty states.
   Stacks full-width on mobile, fixed comfortable width ≥ sm. ───── */
.btn-action {
    width: 100%;
    max-width: 320px;
}

@media (min-width: 640px) {
    .btn-action {
        width: 14rem;
        max-width: none;
    }
}

/* ── Rasmlarni sudrab olishni (drag) o'chirish ───────────────────
   `-webkit-user-drag` Chrome/Safari uchun ishlaydi; Firefox uni
   qo'llab-quvvatlamaydi, shuning uchun base.js'da `dragstart`
   hodisasi ham bloklanadi. */
img,
picture,
svg {
    -webkit-user-drag: none;
    -khtml-user-drag: none;
    -moz-user-drag: none;
    user-drag: none;
    -webkit-touch-callout: none;
}

/* ── Code blocks ────────────────────────────────────────────────────
   These live in the base bundle because a post can be opened through HTMX.
   In that path the body is swapped before page-specific styles finish
   merging; keeping the layout here prevents the language/copy toolbar from
   being positioned against the page instead of its code block. */
code[class*="language-"],
pre[class*="language-"] {
    color: #ccc;
    font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
    font-size: 1em;
    text-align: left;
    word-spacing: normal;
    word-break: normal;
    word-wrap: normal;
    line-height: 1.5;
    tab-size: 4;
    hyphens: none;
}

pre[class*="language-"] {
    margin: 1rem 0 !important;
    border-radius: 8px !important;
    background: #0d1b2a !important;
    border: 1px solid rgba(103, 232, 249, 0.15) !important;
    position: relative;
    padding: 2.5rem 1em 1em !important;
    overflow: auto;
    white-space: pre-wrap !important;
    word-wrap: break-word !important;
    word-break: break-word !important;
}

:not(pre) > code[class*="language-"] {
    padding: 0.1em;
    border-radius: 0.3em;
    white-space: normal;
    background: #2d2d2d;
}

/* Keep the generated toolbar anchored to every enhanced code block. */
.blog-content pre:has(> .code-header) {
    position: relative;
}

.code-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    padding: 0.4rem 0.75rem;
    z-index: 10;
}

.code-lang,
.copy-btn {
    font-size: 0.68rem;
    line-height: 1;
    color: var(--text-soft);
    background: rgba(15, 23, 42, 0.85);
    border: 1px solid rgba(148, 163, 184, 0.35);
    padding: 0.2rem 0.45rem;
    border-radius: 9999px;
}

.code-lang {
    letter-spacing: 0.03em;
    text-transform: uppercase;
}

.copy-btn {
    cursor: pointer;
    transition:
        color 0.2s ease,
        background 0.2s ease,
        border-color 0.2s ease,
        transform var(--dur-release) var(--ease-spring);
    -webkit-tap-highlight-color: transparent;
    -webkit-user-select: none;
    user-select: none;
}

.copy-btn:hover {
    color: var(--accent-300);
    border-color: rgba(103, 232, 249, 0.5);
}

.copy-btn:active {
    transform: scale(0.92) translateZ(0);
    transition: transform var(--dur-tap) var(--ease-standard);
}

.copy-btn.copied {
    color: var(--ok);
    border-color: rgba(74, 222, 128, 0.5);
}

pre[class*="language-"]::before {
    display: none !important;
}

pre[class*="language-"] code {
    font-size: 0.92rem;
    background: transparent !important;
    white-space: pre-wrap !important;
    word-wrap: break-word !important;
    word-break: break-word !important;
}

.token.block-comment,
.token.cdata,
.token.comment,
.token.doctype,
.token.prolog { color: #999; }
.token.punctuation { color: #ccc; }
.token.attr-name,
.token.deleted,
.token.namespace,
.token.tag { color: #e2777a; }
.token.function-name { color: #6196cc; }
.token.boolean,
.token.function,
.token.number { color: #f08d49; }
.token.class-name,
.token.constant,
.token.property,
.token.symbol { color: #f8c555; }
.token.atrule,
.token.builtin,
.token.important,
.token.keyword,
.token.selector { color: #cc99cd; }
.token.attr-value,
.token.char,
.token.regex,
.token.string,
.token.variable { color: #7ec699; }
.token.entity,
.token.operator,
.token.url { color: #67cdcc; }
.token.bold,
.token.important { font-weight: 700; }
.token.italic { font-style: italic; }
.token.entity { cursor: help; }
.token.inserted { color: green; }
