/* ============================================================
   CV Builder — Visual Effects
   Applied to public CV sections and the builder preview.
   ============================================================ */

/* ------ Circular colour swatches ------ */
.color-swatch {
    appearance: none;
    -webkit-appearance: none;
    width: 32px;
    height: 32px;
    border-radius: 9999px;
    border: 2px solid #d1d5db;
    padding: 0;
    cursor: pointer;
    background: none;
    transition: transform 0.12s ease, border-color 0.12s ease, box-shadow 0.12s ease;
}
.color-swatch::-webkit-color-swatch-wrapper { padding: 0; }
.color-swatch::-webkit-color-swatch { border: none; border-radius: 9999px; }
.color-swatch::-moz-color-swatch { border: none; border-radius: 9999px; }
.color-swatch:hover { transform: scale(1.12); border-color: #9ca3af; }
.color-swatch:focus { outline: none; }
.color-swatch:focus-visible { outline: 3px solid #fadb5f; outline-offset: 2px; }

/* ------ Shared surfaces / theme variables ------ */
.cv-theme-light,
.cv-theme-dark {
    color: var(--cv-text-colour, #111827);
}

.cv-surface {
    background: var(--cv-card-bg, #ffffff);
}

.cv-section-border {
    border: 1px solid var(--border-colour, var(--cv-card-border, #e5e7eb));
}

.cv-text-white {
    color: #f9fafb;
}

.cv-text-black {
    color: #111827;
}

.cv-muted {
    color: var(--cv-muted-colour, #6b7280);
}

.cv-builder-sidebar,
.cv-builder-panel {
    --cv-card-bg: #ffffff;
    --cv-card-border: #e5e7eb;
    --cv-text-colour: #111827;
    --cv-muted-colour: #6b7280;
    color: #374151;
    color-scheme: light;
}

.cv-builder-sidebar .cv-theme-input,
.cv-builder-panel .cv-theme-input {
    background-color: #ffffff;
    border-color: #e5e7eb;
    color: #374151;
}

/* Range inputs are excluded: forcing a white background paints over the native
   track, leaving an invisible slider. They are styled by .cv-range below. */
.cv-builder-sidebar select,
.cv-builder-sidebar input:not([type="color"]):not([type="checkbox"]):not([type="hidden"]):not([type="range"]),
.cv-builder-panel select,
.cv-builder-panel input:not([type="color"]):not([type="checkbox"]):not([type="hidden"]):not([type="range"]) {
    background-color: #ffffff;
    color: #374151;
    color-scheme: light;
}

.cv-range {
    width: 100%;
    height: 1.25rem;
    accent-color: #2c2c2c;
    background: transparent;
    cursor: pointer;
}

.cv-range:focus-visible {
    outline: 2px solid #2c2c2c;
    outline-offset: 2px;
    border-radius: 9999px;
}

.cv-builder-sidebar {
    /* Clean, pure-white panel — it floats on the blue shell with a shadow, so
       no border is needed. */
    background: #ffffff;
}

.cv-builder-toolbar {
    background: #ffffff;
    border: 1px solid #e5e7eb;
}

.cv-builder-panel {
    background: #ffffff;
    border: 1px solid #eaecf2;
}

.cv-builder-panel-empty {
    color: var(--cv-muted-colour, #6b7280);
}

.cv-theme-input {
    border: 1px solid color-mix(in srgb, var(--cv-card-border, #e5e7eb) 80%, var(--cv-secondary-colour, #e0e7ff));
    background: color-mix(in srgb, var(--cv-card-bg, #ffffff) 96%, var(--cv-secondary-colour, #e0e7ff));
}

.cv-builder-panel-header {
    background: rgba(232, 234, 240, 0.97);
}

.cv-builder-panel-card {
    background: #ffffff;
    border: 1px solid #e5e7eb;
}

/* Glow preset + colour only take effect when the glow border is enabled.
   Dim and disable them while the toggle is off so the dependency is obvious. */
.section-fx-group:has(input[name="glow_border"]:not(:checked)) .glow-dependent {
    opacity: 0.4;
    pointer-events: none;
}

/* ──────────────────────────────────────────────────────────────
   Builder canvas — the live CV preview. A recessed, clearly-framed
   "window" so it's obvious what will and won't appear on the
   published CV, regardless of the CV's own background colour.
   ────────────────────────────────────────────────────────────── */
.cv-builder-canvas {
    box-shadow:
        inset 0 0 0 1px rgba(17, 24, 39, 0.12),
        inset 9px 0 18px -14px rgba(17, 24, 39, 0.28),
        inset -9px 0 18px -14px rgba(17, 24, 39, 0.28);
}

/* Section cards inside the builder always show a defined outline so each
   block reads as a distinct card, independent of its own border setting.
   Scoped to the builder only — the public CV is untouched. */
.cv-builder-canvas .section-card {
    box-shadow: 0 1px 2px rgba(16, 24, 39, 0.06);
}

.cv-builder-canvas .section-card.cv-section-border {
    border-color: color-mix(in srgb, var(--cv-card-border, #e5e7eb) 45%, #9aa1ad);
}

.cv-builder-canvas .section-card:not(.cv-section-border):not(.glow-border) {
    border: 1px dashed rgba(17, 24, 39, 0.16);
}

.cv-soft-chip {
    background: color-mix(in srgb, var(--cv-secondary-colour, #e0e7ff) 20%, transparent);
    border: 1px solid color-mix(in srgb, var(--cv-secondary-colour, #e0e7ff) 55%, var(--cv-card-border, #e5e7eb));
    color: var(--cv-text-colour, #111827);
}

.cv-accent-text {
    color: color-mix(in srgb, var(--cv-secondary-colour, #e0e7ff) 70%, var(--cv-text-colour, #111827));
}

.cv-primary-text {
    color: color-mix(in srgb, var(--cv-primary-colour, #6366f1) 82%, var(--cv-text-colour, #111827));
}

.cv-accent-link {
    color: color-mix(in srgb, var(--cv-secondary-colour, #e0e7ff) 78%, var(--cv-text-colour, #111827));
    transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
}

.cv-accent-link:hover {
    color: var(--cv-text-colour, #111827);
}

.cv-header-link {
    color: color-mix(in srgb, var(--cv-secondary-colour, #e0e7ff) 78%, var(--cv-text-colour, #111827));
    transition: color 0.15s ease;
}

.cv-header-link:hover {
    color: var(--cv-text-colour, #111827);
}

.cv-flat-card {
    box-shadow: none !important;
}

.cv-transparent-section {
    min-height: 2rem;
}

.cv-builder-transparent {
    outline: 1px dashed rgba(0, 0, 0, 0.15);
    outline-offset: -1px;
}

.cv-side-panel {
    position: absolute;
    top: 0;
    bottom: 0;
    width: calc(
        max(0px, (100% - min(100%, calc(var(--cv-content-max-width, 56rem) + (var(--cv-content-shell-padding, 1.5rem) * 2)))) / 2)
        + (min(100%, calc(var(--cv-content-max-width, 56rem) + (var(--cv-content-shell-padding, 1.5rem) * 2))) * var(--cv-side-panel-ratio, 0.25))
    );
    background: var(
        --cv-side-panel-background,
        var(--cv-side-panel-colour, var(--cv-secondary-colour, #e0e7ff))
    );
    opacity: 1;
    z-index: 0;
    pointer-events: none;
    border-color: color-mix(in srgb, var(--cv-side-panel-colour, var(--cv-secondary-colour, #e0e7ff)) 65%, var(--cv-card-border, #e5e7eb));
}

.cv-side-panel-left {
    left: 0;
    border-right-width: var(--cv-side-panel-border-width, 1px);
}

.cv-side-panel-right {
    right: 0;
    border-left-width: var(--cv-side-panel-border-width, 1px);
}

/* ══════════════════════════════════════════════════════════════
   Two-column public layout (accent band on)
   ──────────────────────────────────────────────────────────────
   A ~75% main column + a ~25% sidebar that lines up with the accent
   band. Quarter-width sections flow into the sidebar; everything
   else lays out inside the main column, each column flowing at its
   own height. Single column on narrow screens.

   The band column is FIXED at 25% — that is the whole point of the
   band — so the only widths a section can take are the three that
   fit the remaining 75%: 75% (the full main column), 50% (two
   thirds of it) and 25% (one third). Those are the .cv-main-*
   classes below, and they are named for their share of the *page*,
   not of the column, so the number in the builder's width badge is
   the number the reader actually sees.
   ══════════════════════════════════════════════════════════════ */
.cv-layout-split {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    column-gap: var(--cv-col-gap, 1.5rem);
    row-gap: var(--cv-section-gap-y, 1.5rem);
    align-items: start;
}
@media (min-width: 768px) {
    .cv-layout-split {
        grid-template-columns: minmax(0, 3fr) minmax(0, 1fr);
    }
}
.cv-col-main,
.cv-col-side {
    min-width: 0;
}
/* Main column: a wrapping row so two or three sections can share a line.
   Sidebar stays a simple stack — it is one section wide by definition. */
.cv-col-main {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    column-gap: var(--cv-col-gap, 1.5rem);
    row-gap: var(--cv-section-gap-y, 1.5rem);
}
.cv-col-main > * {
    width: 100%;
}
.cv-col-side > * + * {
    margin-top: var(--cv-section-gap-y, 1.5rem);
}
.cv-section-region {
    padding-block: var(--cv-section-gap-y, 1.5rem);
}
.cv-public-section-grid {
    column-gap: 1.5rem;
    row-gap: var(--cv-section-gap-y, 1.5rem);
}
/* Three tracks of gap g: one third == (100% - 2g)/3, two thirds == that × 2 + g.
   Written out so the gap stays a single variable both renderers can retune. */
@media (min-width: 768px) {
    .cv-col-main > .cv-main-75 {
        width: 100%;
    }
    .cv-col-main > .cv-main-50 {
        width: calc(66.6667% - var(--cv-col-gap, 1.5rem) / 3);
    }
    .cv-col-main > .cv-main-25 {
        width: calc(33.3333% - var(--cv-col-gap, 1.5rem) * 2 / 3);
    }
}

/* Sidebar sections sit on top of the accent band, and the band's left edge lands
   only ~1rem from where their default p-6 padding starts — close enough that the
   text reads as crowded against it. Widen the inset so the column breathes
   inside the band. Same specificity as Tailwind's .p-6, and this file loads
   after tailwind.css, so it wins; only from the breakpoint where the two-column
   layout actually exists, since below it the band isn't beside the content. */
@media (min-width: 768px) {
    .cv-side-item {
        padding-left: 2.75rem;
        padding-right: 1.25rem;
    }

    /* Keep real logos and fallback monograms visible in the narrow band. Both
       shrink to one compact rounded tile, preserving the same entry language as
       the main column without crowding the text. */
    .cv-side-item .cv-entry-logo {
        width: 2.25rem;
        height: 2.25rem;
        border-radius: 0.5rem;
        font-size: 0.875rem;
    }
    .cv-side-item .cv-entry {
        gap: 0.7rem;
    }
    .cv-side-item .cv-entry-head {
        flex-wrap: wrap;
        gap: 0 0.5rem;
    }

    /* Certification and project images/monograms use the same compact rounded
       tile in the sidebar. */
    .cv-side-item .cv-cert-thumb-slot,
    .cv-side-item .cv-sideproj-thumb {
        width: 2.5rem;
        height: 2.5rem;
        border-radius: 0.5rem;
    }
    .cv-side-item .cv-sideproj {
        gap: 0.75rem;
    }
}

/* ══════════════════════════════════════════════════════════════
   Section spotlight removed — sections no longer self-highlight
   based on scroll position. Per-section glow settings remain the
   only source of card highlighting.
   ══════════════════════════════════════════════════════════════ */

/* ══════════════════════════════════════════════════════════════
   Effect preview popups (builder edit panel)
   An eye icon next to each effect; hovering/focusing it reveals a
   small live demo of the effect so the user knows what it does.
   ══════════════════════════════════════════════════════════════ */
.fx-preview {
    position: relative;
    display: inline-flex;
    flex-shrink: 0;
}

.fx-eye {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    border-radius: 8px;
    color: #9ca3af;
    border: 1px solid transparent;
    background: transparent;
    cursor: help;
    transition: color 0.12s ease, background-color 0.12s ease, border-color 0.12s ease;
}
.fx-eye:hover,
.fx-eye:focus-visible {
    color: #4b5563;
    background: #ffffff;
    border-color: #e5e7eb;
    outline: none;
}

.fx-pop {
    position: absolute;
    right: 0;
    top: calc(100% + 8px);
    z-index: 60;
    width: 210px;
    padding: 10px;
    background: #ffffff;
    /* Flat popover: crisp edge + amber cap, no floating shadow. */
    border: 1px solid #c9d2e3;
    border-top: 3px solid #fbcb2b;
    border-radius: 12px;
    opacity: 0;
    visibility: hidden;
    transform: translateY(-4px);
    transition: opacity 0.15s ease, transform 0.15s ease, visibility 0.15s;
}
.fx-preview:hover .fx-pop,
.fx-preview:focus-within .fx-pop {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
}

.fx-pop-label {
    display: block;
    font-size: 9px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: #9ca3af;
    margin-bottom: 6px;
}

.fx-demo {
    display: block;
    margin-top: 8px;
    padding: 8px 10px;
    border-radius: 10px;
    background: #ffffff;
    border: 1px solid #e5e7eb;
    font-size: 11px;
    line-height: 1.3;
    color: #374151;
    text-align: center;
}
.fx-demo:first-of-type {
    margin-top: 0;
}

/* Looping fade demo (the real entrance-fade only plays once on load) */
@keyframes fx-demo-fade {
    0% { opacity: 0; transform: translateY(10px); }
    55%, 100% { opacity: 1; transform: translateY(0); }
}
.fx-demo-fade {
    animation: fx-demo-fade 2.4s ease-in-out infinite;
}

/* Centre a single demo object (avatar ring) */
.fx-demo-center {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 12px 10px;
}

/* Banner demo tile — fills edge-to-edge so the shine sweep reads clearly */
.fx-demo-banner {
    display: block;
    height: 42px;
    border-radius: 10px;
    border: 1px solid #e5e7eb;
    background: linear-gradient(135deg, #6366f1 0%, #a855f7 100%);
}

/* ══════════════════════════════════════════════════════════════
   Quick-pick theme colour chips (builder colour fields)
   Pull the CV's primary / secondary colour into a colour input.
   ══════════════════════════════════════════════════════════════ */
.cv-quickchip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 32px;
    padding: 0 12px;
    border-radius: 9999px;
    border: 1px solid #e5e7eb;
    background: #ffffff;
    font-size: 11px;
    font-weight: 500;
    color: #4b5563;
    cursor: pointer;
    transition: border-color 0.12s ease, background-color 0.12s ease, color 0.12s ease, box-shadow 0.12s ease;
}
.cv-quickchip:hover {
    border-color: #d1d5db;
    background: #f9fafb;
    color: #1f2937;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
}
.cv-quickchip-dot {
    width: 12px;
    height: 12px;
    border-radius: 9999px;
    flex-shrink: 0;
    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.12);
}
.cv-quickchip-primary {
    background: var(--cv-primary-colour, #6366f1);
}
.cv-quickchip-secondary {
    background: var(--cv-secondary-colour, #e0e7ff);
}

/* ------ Skill chips (theme-aware) ------ */
.cv-skill-chip {
    background: color-mix(in srgb, var(--cv-primary-colour, #6366f1) 12%, var(--cv-card-bg, #fff));
    color: color-mix(in srgb, var(--cv-primary-colour, #6366f1) 85%, var(--cv-text-colour, #111827));
    ring: 1px solid color-mix(in srgb, var(--cv-primary-colour, #6366f1) 25%, transparent);
    box-shadow: 0 0 0 1px color-mix(in srgb, var(--cv-primary-colour, #6366f1) 22%, transparent);
}
/* Transparent (outline) variant: drop the tinted fill but keep a crisp hairline
   ring so the chip still reads as a discrete token. Text colour is inherited
   from the base, which already mixes toward the primary. */
.cv-skill-chip--flat {
    background: transparent;
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--cv-primary-colour, #6366f1) 34%, transparent);
}
/* Hover switches the pill to the theme's secondary colour, then lifts it on
   a restrained matching bloom. */
.cv-skill-chip {
    transition: background-color 0.18s ease, color 0.18s ease,
        box-shadow 0.18s ease, transform 0.18s ease;
}
.cv-skill-chip:hover {
    color: color-mix(in srgb, var(--cv-secondary-colour, #06b6d4) 88%, var(--cv-text-colour, #111827));
    background: color-mix(in srgb, var(--cv-secondary-colour, #06b6d4) 22%, var(--cv-card-bg, transparent));
    transform: translateY(-1px);
    box-shadow:
        0 0 0 1px color-mix(in srgb, var(--cv-secondary-colour, #06b6d4) 48%, transparent),
        0 0 12px -1px color-mix(in srgb, var(--cv-secondary-colour, #06b6d4) 38%, transparent);
}

/* ------ Entry logo: company (experience) / institution (education) ------ */
/* Deliberately borderless with a transparent backing so a PNG logo keeps its
   own transparency and sits cleanly on the card. `contain` (not cover) shows
   the whole mark without cropping wide or tall logos. */
.cv-entry-logo {
    width: 2.75rem;
    height: 2.75rem;
    border-radius: 0.625rem;
    object-fit: contain;
    flex-shrink: 0;
    background: transparent;
    border: 0;
}
/* Fallback first-letter tiles. Deliberately ONE look for every monogram on the
   page — experience, education, certifications and projects differ only in size
   — so the letter tiles read as a single system rather than three near-misses.
   Borderless, to preserve the transparent-card look of flat templates.

   The tint is mixed with `transparent`, not with --cv-card-bg. On a template
   whose sections are transparent, a white-based tint paints an opaque grey box
   onto the page; a translucent wash instead takes the colour of whatever is
   actually behind it — warm paper, a white card, or dark stock. */
.cv-entry-logo--empty,
.cv-entry-monogram {
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    line-height: 1;
    text-transform: uppercase;
    border-radius: 0.625rem;
    color: color-mix(in srgb, var(--cv-primary-colour, #6366f1) 82%, var(--cv-text-colour, #111827));
    background: linear-gradient(
        135deg,
        color-mix(in srgb, var(--cv-primary-colour, #6366f1) 16%, transparent),
        color-mix(in srgb, var(--cv-secondary-colour, #a5b4fc) 13%, transparent)
    );
}
.cv-entry-logo--empty {
    font-size: 1.05rem;
}
.cv-entry-monogram {
    font-size: 0.9rem;
}
/* Side-project image or monogram beside the text. Rounded like Experience and
   Education so every entry-preview affordance belongs to one visual system. */
.cv-sideproj-thumb {
    width: 3.25rem;
    height: 3.25rem;
    flex-shrink: 0;
    border-radius: 0.625rem;
    object-fit: cover;
}

/* Divided entry list for experience / education. Adjacent-sibling rule so the
   first entry has no top rule — a clean, modern separated stack. */
.cv-entry {
    display: flex;
    gap: 0.875rem;
}
.cv-entry + .cv-entry {
    margin-top: 1.15rem;
    padding-top: 1.15rem;
    border-top: 1px solid color-mix(in srgb, var(--cv-card-border, #e5e7eb) 70%, transparent);
}

/* ------ Link rows (theme-aware) ------ */
.cv-link-row {
    background: color-mix(in srgb, var(--cv-card-bg, #fff) 92%, var(--cv-primary-colour, #6366f1));
    border: 1px solid color-mix(in srgb, var(--cv-card-border, #e5e7eb) 80%, var(--cv-primary-colour, #6366f1));
    color: var(--cv-text-colour, #111827);
    transition: background 0.15s ease, border-color 0.15s ease;
}
.cv-link-row:hover {
    background: color-mix(in srgb, var(--cv-card-bg, #fff) 80%, var(--cv-primary-colour, #6366f1));
    border-color: color-mix(in srgb, var(--cv-card-border, #e5e7eb) 50%, var(--cv-primary-colour, #6366f1));
}
/* ══════════════════════════════════════════════════════════════
   Glow border presets — four deliberately distinct looks.
   Both .glow-border (base = "Default") and .glow-preset-* are
   applied together; preset rules come after the base so they win.
   ══════════════════════════════════════════════════════════════ */

/* Default — a crisp ring with a clean, steady bloom. Static. */
.glow-border {
    box-shadow:
        0 0 0 1.5px var(--glow-colour, #6366f1),
        0 0 16px 2px color-mix(in srgb, var(--glow-colour, #6366f1) 38%, transparent);
    border-color: var(--glow-colour, #6366f1) !important;
}

/* Subtle — no hard ring, just a soft diffuse halo. The understated option. */
.glow-preset-subtle {
    box-shadow:
        0 0 0 1px color-mix(in srgb, var(--glow-colour, #6366f1) 28%, transparent),
        0 0 24px 6px color-mix(in srgb, var(--glow-colour, #6366f1) 13%, transparent);
    border-color: color-mix(in srgb, var(--glow-colour, #6366f1) 50%, transparent) !important;
    filter: none;
}

/* Neon — a bright electric tube that breathes. The solid (non-transparent)
   inner shadow is what gives it the lit-glass-tube core. */
.glow-preset-neon {
    border-color: var(--glow-colour, #6366f1) !important;
    filter: saturate(1.6) brightness(1.08);
    animation: glow-neon-pulse 1.8s ease-in-out infinite;
}

@keyframes glow-neon-pulse {
    0%, 100% {
        box-shadow:
            0 0 0 1px var(--glow-colour, #6366f1),
            0 0 6px 1px var(--glow-colour, #6366f1),
            0 0 18px 4px color-mix(in srgb, var(--glow-colour, #6366f1) 60%, transparent);
    }
    50% {
        box-shadow:
            0 0 0 1.5px var(--glow-colour, #6366f1),
            0 0 13px 3px var(--glow-colour, #6366f1),
            0 0 36px 11px color-mix(in srgb, var(--glow-colour, #6366f1) 78%, transparent);
    }
}

/* Rainbow — ignores the single glow colour and cycles the spectrum. The ring
   (the 1.5px spread) carries the colour, so the real border is left transparent
   (border-color can't be animated via !important inside @keyframes). */
.glow-preset-rainbow {
    border-color: transparent !important;
    filter: saturate(1.25);
    animation: glow-rainbow-cycle 6s linear infinite;
}

@keyframes glow-rainbow-cycle {
    0% {
        box-shadow: 0 0 0 1.5px #ff3b30, 0 0 22px 5px color-mix(in srgb, #ff3b30 55%, transparent);
    }
    16% {
        box-shadow: 0 0 0 1.5px #ff9500, 0 0 22px 5px color-mix(in srgb, #ff9500 55%, transparent);
    }
    33% {
        box-shadow: 0 0 0 1.5px #ffcc00, 0 0 22px 5px color-mix(in srgb, #ffcc00 55%, transparent);
    }
    50% {
        box-shadow: 0 0 0 1.5px #34c759, 0 0 22px 5px color-mix(in srgb, #34c759 55%, transparent);
    }
    66% {
        box-shadow: 0 0 0 1.5px #0a84ff, 0 0 22px 5px color-mix(in srgb, #0a84ff 55%, transparent);
    }
    83% {
        box-shadow: 0 0 0 1.5px #af52de, 0 0 22px 5px color-mix(in srgb, #af52de 55%, transparent);
    }
    100% {
        box-shadow: 0 0 0 1.5px #ff3b30, 0 0 22px 5px color-mix(in srgb, #ff3b30 55%, transparent);
    }
}

/* Respect reduced-motion: drop the animation, keep a strong static glow. */
@media (prefers-reduced-motion: reduce) {
    .glow-preset-neon {
        animation: none;
        box-shadow:
            0 0 0 1.5px var(--glow-colour, #6366f1),
            0 0 12px 3px var(--glow-colour, #6366f1),
            0 0 30px 9px color-mix(in srgb, var(--glow-colour, #6366f1) 68%, transparent);
    }
    .glow-preset-rainbow {
        animation: none;
        box-shadow:
            0 0 0 1.5px #0a84ff,
            0 0 22px 5px color-mix(in srgb, #0a84ff 55%, transparent);
    }
}

.shine-border {
    position: relative;
    overflow: hidden;
}

.shine-border::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(
        118deg,
        transparent 8%,
        color-mix(in srgb, var(--cv-primary-colour, #6366f1) 18%, transparent) 28%,
        color-mix(in srgb, var(--cv-secondary-colour, #e0e7ff) 55%, white) 50%,
        color-mix(in srgb, var(--cv-primary-colour, #6366f1) 18%, transparent) 72%,
        transparent 92%
    );
    transform: translateX(-130%) skewX(-12deg);
    animation: border-shine 4.2s ease-in-out infinite;
    pointer-events: none;
    mix-blend-mode: overlay;
    opacity: 0.9;
}

@keyframes border-shine {
    0%, 45% {
        transform: translateX(-130%) skewX(-12deg);
        opacity: 0;
    }
    52% {
        opacity: 1;
    }
    75%, 100% {
        transform: translateX(130%) skewX(-12deg);
        opacity: 0;
    }
}

/* ------ Entrance fade (triggered by IntersectionObserver in effects.js) ------ */
.entrance-fade {
    opacity: 0;
    transform: translateY(16px);
    transition: opacity 0.6s ease, transform 0.6s ease;
}

.entrance-fade.is-visible {
    opacity: 1;
    transform: translateY(0);
}

/* ------ Animated glow around the profile avatar ------ */
@keyframes avatar-glow {
    0%, 100% {
        box-shadow: 0 0 var(--avatar-glow-blur-min, 10px) var(--avatar-glow-spread-min, 3px) var(--glow-colour, #6366f1);
    }
    50% {
        box-shadow: 0 0 var(--avatar-glow-blur-max, 24px) var(--avatar-glow-spread-max, 8px) var(--glow-colour, #6366f1);
    }
}

.glow-avatar {
    --avatar-glow-blur-min: 10px;
    --avatar-glow-blur-max: 24px;
    --avatar-glow-spread-min: 3px;
    --avatar-glow-spread-max: 8px;
    animation: avatar-glow 2.5s ease-in-out infinite;
}

.glow-preset-subtle .glow-avatar,
.glow-avatar.glow-preset-subtle {
    --avatar-glow-blur-min: 8px;
    --avatar-glow-blur-max: 16px;
    --avatar-glow-spread-min: 2px;
    --avatar-glow-spread-max: 4px;
}

.glow-preset-neon .glow-avatar,
.glow-avatar.glow-preset-neon {
    --avatar-glow-blur-min: 12px;
    --avatar-glow-blur-max: 30px;
    --avatar-glow-spread-min: 4px;
    --avatar-glow-spread-max: 10px;
}

.glow-preset-rainbow .glow-avatar,
.glow-avatar.glow-preset-rainbow {
    --avatar-glow-blur-min: 12px;
    --avatar-glow-blur-max: 28px;
    --avatar-glow-spread-min: 4px;
    --avatar-glow-spread-max: 9px;
}

/* ------ Social link pills (theme-aware) ------
   Previously fixed white-on-grey, which read as generic app chrome bolted onto
   the resume and disappeared on a dark template. Now the pill is a primary
   wash with a primary hairline; hover hands over to the secondary colour, so
   the two accents of the palette both do a job. Every tint mixes with
   `transparent` so the pill sits on the page/banner it happens to overlap
   rather than punching an opaque plate through it. */
.cv-social-pill {
    color: color-mix(in srgb, var(--cv-primary-colour, #6366f1) 78%, var(--cv-text-colour, #111827));
    background: color-mix(in srgb, var(--cv-primary-colour, #6366f1) 9%, transparent);
    border: 1px solid color-mix(in srgb, var(--cv-primary-colour, #6366f1) 24%, transparent);
    text-decoration: none;
    transition: color 0.16s ease, border-color 0.16s ease, background-color 0.16s ease,
                box-shadow 0.16s ease, transform 0.16s ease;
}
.cv-social-pill:hover {
    color: color-mix(in srgb, var(--cv-secondary-colour, #a5b4fc) 62%, var(--cv-text-colour, #111827));
    background: color-mix(in srgb, var(--cv-secondary-colour, #a5b4fc) 15%, transparent);
    border-color: color-mix(in srgb, var(--cv-secondary-colour, #a5b4fc) 52%, transparent);
    transform: translateY(-1px);
    box-shadow: 0 4px 12px color-mix(in srgb, var(--cv-primary-colour, #6366f1) 16%, transparent);
}
/* Keyboard parity with the hover state — the pill is a real link. */
.cv-social-pill:focus-visible {
    outline: 2px solid color-mix(in srgb, var(--cv-secondary-colour, #a5b4fc) 70%, transparent);
    outline-offset: 2px;
}
/* Inline SVG mark: inherits `color` from the pill, so it tints with the theme.
   Held slightly back at rest so the label leads, full strength on hover. */
.cv-social-icon {
    width: 0.95rem;
    height: 0.95rem;
    flex-shrink: 0;
    opacity: 0.82;
    transition: opacity 0.16s ease;
}
.cv-social-pill:hover .cv-social-icon {
    opacity: 1;
}

/* ------ Social icon idle Y-axis flip + stagger ------ */
.social-icon-spin {
    display: inline-block;
    animation: icon-idle-flipY 6s ease-in-out infinite;
}

/* Each icon flips at a different phase so they feel individual */
.cv-social-pill:nth-child(1) .social-icon-spin { animation-delay: 0s; }
.cv-social-pill:nth-child(2) .social-icon-spin { animation-delay: 1.2s; }
.cv-social-pill:nth-child(3) .social-icon-spin { animation-delay: 2.4s; }
.cv-social-pill:nth-child(4) .social-icon-spin { animation-delay: 3.6s; }
.cv-social-pill:nth-child(5) .social-icon-spin { animation-delay: 4.8s; }

@keyframes icon-idle-flipY {
    0%, 78%  { transform: perspective(180px) rotateY(0deg); }
    88%      { transform: perspective(180px) rotateY(180deg); }
    100%     { transform: perspective(180px) rotateY(360deg); }
}

/* On hover: fast snappy flip, overrides idle */
.cv-social-pill:hover .social-icon-spin {
    animation: icon-hover-flipY 0.55s cubic-bezier(0.22, 0.68, 0, 1.2) forwards;
}

@keyframes icon-hover-flipY {
    0%   { transform: perspective(180px) rotateY(0deg) scale(1); }
    50%  { transform: perspective(180px) rotateY(180deg) scale(1.2); }
    100% { transform: perspective(180px) rotateY(360deg) scale(1); }
}

/* ------ Confirm modal ------ */
.confirm-modal-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(15, 23, 42, 0.55);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.18s ease;
}

.confirm-modal-backdrop.is-open {
    opacity: 1;
    pointer-events: auto;
}

.confirm-modal-panel {
    transform: translateY(16px) scale(0.98);
    transition: transform 0.18s ease;
}

.confirm-modal-backdrop.is-open .confirm-modal-panel {
    transform: translateY(0) scale(1);
}

/* Amber "caution" badge for the confirm dialog — the gold half of the brand
   palette. Hand-authored because the prebuilt tailwind.css doesn't ship the
   accent-100/200/700 utilities this uses. */
.confirm-modal-badge {
    background: #fff3c4;                    /* accent-100 */
    color: #b45309;                         /* deep amber */
    box-shadow: inset 0 0 0 1px #fde68a;    /* accent-200 ring */
}

/* ──────────────────────────────────────────────────────────────
   Banner shine sweep
   ────────────────────────────────────────────────────────────── */
.cv-banner-shine {
    position: relative;
    overflow: hidden;
}

.cv-banner-shine::after {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: -60%;
    width: 35%;
    background: linear-gradient(
        105deg,
        transparent 0%,
        color-mix(in srgb, white 6%, transparent) 30%,
        color-mix(in srgb, white 20%, transparent) 50%,
        color-mix(in srgb, white 6%, transparent) 70%,
        transparent 100%
    );
    transform: skewX(-18deg);
    animation: banner-shine-sweep 9s ease-in-out infinite;
    pointer-events: none;
}

@keyframes banner-shine-sweep {
    0%, 50% { left: -60%; opacity: 0; }
    55%     { opacity: 1; }
    82%     { opacity: 1; }
    90%, 100% { left: 130%; opacity: 0; }
}

/* ──────────────────────────────────────────────────────────────
   Spinning gradient ring around the avatar
   ────────────────────────────────────────────────────────────── */
.cv-avatar-outer {
    position: relative;
    display: inline-block;
    /* Radius tracks the chosen avatar shape (--cv-avatar-radius, emitted by the
       profile_layout tag). The ring sits 3px outside the image, so its own
       radius has to grow by the same amount or the corners pinch on a square
       avatar. calc() is safe with the 9999px round default too. */
    border-radius: calc(var(--cv-avatar-radius, 9999px) + 3px);
    padding: 3px;
    overflow: hidden;
    isolation: isolate;
    box-shadow:
        0 0 18px color-mix(in srgb, var(--cv-primary-colour, #6366f1) 45%, transparent),
        0 6px 20px rgba(0, 0, 0, 0.25);
    flex-shrink: 0;
}

/* Spin the gradient inside a fixed frame rather than spinning the frame itself.
   Rotating the element was invisible while the avatar was always a circle, but a
   square one visibly tumbled through a diamond — and it forced a matching
   counter-rotation on the image to hold it still, which is now unnecessary.
   The oversized inset keeps the rotating square from ever exposing a bare
   corner inside the clipped frame. */
.cv-avatar-outer::before {
    content: "";
    position: absolute;
    inset: -50%;
    z-index: -1;
    background: conic-gradient(
        from 0deg,
        var(--cv-primary-colour, #6366f1) 0deg,
        var(--cv-secondary-colour, #e0e7ff) 120deg,
        rgba(255, 255, 255, 0.95) 180deg,
        var(--cv-secondary-colour, #e0e7ff) 240deg,
        var(--cv-primary-colour, #6366f1) 360deg
    );
    animation: avatar-ring-spin 5s linear infinite;
}

/* Reduced motion keeps the ring, drops the rotation. */
@media (prefers-reduced-motion: reduce) {
    .cv-avatar-outer::before {
        animation: none;
    }
}

.cv-avatar-img {
    display: block;
    border: 3px solid var(--cv-card-bg, #ffffff);
    border-radius: var(--cv-avatar-radius, 9999px);
}

.cv-avatar-placeholder {
    border: 3px solid var(--cv-card-bg, #ffffff);
}

/* Shape of the avatar itself, driven by --cv-avatar-radius so one variable
   covers the photo, the monogram fallback and the spinning ring together. */
.cv-profile-avatar {
    border-radius: var(--cv-avatar-radius, 9999px);
}

/* Palette-aware monogram avatar, shown when no photo is uploaded. Fills with the
   CV's own primary → secondary gradient and sets the initial in the page
   background colour, which is guaranteed to contrast the primary (the primary is
   the page accent). This replaces a fixed indigo→purple + white-text pair that
   clashed with warmer / lighter palettes — e.g. the newspaper template — and now
   the avatar tracks whichever template and light/dark palette the CV is using. */
.cv-avatar-monogram {
    background-image: linear-gradient(
        135deg,
        var(--cv-primary-colour, #6366f1),
        color-mix(in srgb, var(--cv-secondary-colour, #a5b4fc) 72%, var(--cv-primary-colour, #6366f1))
    );
    color: var(--cv-bg-colour, #ffffff);
}

@keyframes avatar-ring-spin {
    to { transform: rotate(360deg); }
}

/* ══════════════════════════════════════════════════════════════
   Profile header presentation
   ──────────────────────────────────────────────────────────────
   Five independent choices, all resolved by the {% profile_layout %}
   tag into modifier classes on the .cv-profile root:

     container    bleed (default) — banner spans the viewport from the
                  top of the page, exactly as the header always looked
                  card            — the whole header becomes a contained
                  surface inside the content shell
     corner       rounded / square (card mode only; reuses the
                  section's own corner_style)
     banner       header (a strip above the avatar) / full (painted
                  behind the entire header) / none
     text         below the avatar / beside it, in a right-hand column
     avatar       round / square / none

   Every default reproduces the original markup, so a CV that has
   never touched these controls renders unchanged. Heights and radii
   live here rather than as utility classes because the shipped
   tailwind.css is a prebuilt artefact and because the full-cover
   banner has to override the strip's height.
   ══════════════════════════════════════════════════════════════ */
.cv-profile {
    position: relative;
}

/* Bleed: the header element itself spans its container, and only the text is
   constrained to the content shell — so the banner runs edge to edge from the
   top of the page. The geometry reproduces the `max-w-4xl mx-auto px-4 sm:px-6`
   wrapper this markup used to sit in (border-box, so the padding is inside the
   max-width), which is why it lines up with the section grid below. */
.cv-profile--bleed .cv-profile-body {
    max-width: var(--cv-content-max-width, 56rem);
    margin-left: auto;
    margin-right: auto;
    padding-left: 1rem;
    padding-right: 1rem;
}

@media (min-width: 640px) {
    .cv-profile--bleed .cv-profile-body {
        padding-left: 1.5rem;
        padding-right: 1.5rem;
    }
}

/* Card: a contained surface centred in that same shell. Clips the banner to the
   corner radius and floats over the page background — and over the accent band,
   which is why the band text-padding reservation is skipped in this mode. The
   Subtracting the shell padding from the max-width is what lines the card's edges
   up with the section cards below it, and it doubles as the gutter on narrow
   viewports. */
.cv-profile--card {
    max-width: calc(min(var(--cv-content-max-width, 56rem), 100%) - 2rem);
    margin: 1.5rem auto 0;
    background: var(--cv-card-bg, #ffffff);
    border: 1px solid var(--cv-card-border, #e5e7eb);
    border-radius: 28px;
    overflow: hidden;
    box-shadow:
        0 1px 2px rgba(16, 24, 39, 0.06),
        0 18px 40px -30px rgba(16, 24, 39, 0.55);
}

@media (min-width: 640px) {
    .cv-profile--card {
        max-width: calc(min(var(--cv-content-max-width, 56rem), 100%) - 3rem);
        margin-top: 2.5rem;
    }
}

.cv-profile--card.cv-profile--corner-square {
    border-radius: 0;
}

/* Drop the card's lift. The border stays, so the card still reads as contained
   — this is the flat, print-like treatment rather than "no card at all". */
.cv-profile--card.cv-profile--flat {
    box-shadow: none;
}

.cv-profile-banner {
    position: relative;
    overflow: hidden;
    background-size: cover;
    background-position: center;
    opacity: var(--cv-profile-banner-opacity, 1);
}

.cv-profile-banner > img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.cv-profile-banner--header {
    height: 9rem;
}

@media (min-width: 640px) {
    .cv-profile-banner--header {
        height: 12rem;
    }
}

/* Full-cover banner: fills the header and sits behind its content, so the
   opacity slider is what keeps the text legible on top of it. */
.cv-profile-banner--full {
    position: absolute;
    inset: 0;
    z-index: 0;
}

.cv-profile-body {
    position: relative;
    z-index: 1;
}

.cv-profile--card .cv-profile-body {
    padding: 0 1.5rem 1.5rem;
}

/* With no strip above it there is nothing for the avatar to overlap, so the
   content needs its own top inset instead of the pull-up below. */
.cv-profile--banner-none .cv-profile-body,
.cv-profile--banner-full .cv-profile-body {
    padding-top: 1.5rem;
}

.cv-profile--avatar-none.cv-profile--banner-header .cv-profile-body {
    padding-top: 1rem;
}

/* The classic overlap: the avatar rides up over the banner strip. */
.cv-profile--banner-header .cv-profile-avatar-wrap {
    margin-top: -3rem;
}

@media (min-width: 640px) {
    .cv-profile--banner-header .cv-profile-avatar-wrap {
        margin-top: -4rem;
    }
}

/* Text beside the avatar rather than stacked under it. Only the avatar carries
   the pull-up, so the text column starts cleanly below the banner. */
.cv-profile--text-beside .cv-profile-main {
    display: flex;
    align-items: flex-start;
    gap: 1.25rem;
}

@media (min-width: 640px) {
    .cv-profile--text-beside .cv-profile-main {
        gap: 1.5rem;
    }
}

.cv-profile--text-beside .cv-profile-avatar-wrap {
    flex: 0 0 auto;
    /* Keeps a tall photo off the card's bottom edge when the text column is the
       shorter of the two. */
    padding-bottom: 0.5rem;
}

.cv-profile--text-beside .cv-profile-text {
    flex: 1 1 0%;
    min-width: 0;
}

/* Beside + a banner strip: only the avatar carries the pull-up, so the text
   column starts at the row's top — flush against the banner's bottom edge, with
   the name's cap height almost touching it. Inset it so the block breathes. */
.cv-profile--text-beside.cv-profile--banner-header .cv-profile-text {
    padding-top: 1.25rem;
}

/* The builder canvas includes this header unpadded and full-bleed, so both modes
   size themselves from the rules above and the preview matches the published
   page — bleed banners reach the canvas edge, cards sit inset. */

/* ══════════════════════════════════════════════════════════════
   Portfolio / Media Embed — project showcase cards
   ──────────────────────────────────────────────────────────────
   Everything tints from --cv-primary-colour and sits on
   --cv-card-bg / --cv-card-border, so the cards inherit whichever
   light or dark palette the CV is using. Deliberately no hard-coded
   white-on-primary text anywhere: a user can pick a pale primary, and
   white on pale orange/yellow fails contrast. Button labels are always
   mixed towards --cv-text-colour instead, which stays legible against
   any tint the user chooses.
   ══════════════════════════════════════════════════════════════ */

/* Registered so the rail's edge-fade insets are type-checked <length>s with a
   defined 0px starting value — the mask below is then always valid, even in the
   frame before effects.js first measures the scroll position. The var() calls
   also carry a 0px fallback, so browsers without @property behave the same. */
@property --pf-fade-start {
    syntax: "<length>";
    inherits: false;
    initial-value: 0px;
}
@property --pf-fade-end {
    syntax: "<length>";
    inherits: false;
    initial-value: 0px;
}

/* Default: cards wrap into as many columns as fit. */
.pf-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
    gap: 0.875rem;
}

/* Opt-in single-row rail (section style ``portfolio_scroll``). The top padding
   leaves room for the hover lift, which would otherwise be clipped — a flex
   container with overflow-x:auto computes overflow-y to auto too. */
.pf-rail {
    display: flex;
    flex-direction: row;
    gap: var(--pf-gap, 0.875rem);
    overflow-x: auto;
    padding: 0.25rem 0 0.5rem;
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
    /* No visible scrollbar: the edge fade, the drift and the pagination dots
       already signal "there's more", and a scrollbar track under the cards reads
       as clutter. Scrolling (wheel / trackpad / touch / keyboard) still works. */
    scrollbar-width: none;
    -ms-overflow-style: none;
    --pf-gap: 0.875rem;
    /* Cards are sized so a whole number of them fits the visible width — a fixed
       card width can't do that, because the rail's width changes with the layout
       (a 75% main column next to the accent band, or the full content width with
       the band off) and any leftover shows as a half-cut card. */
    --pf-per-view: 1.12;      /* narrow screens: one card + a hint of the next */
    /* Soft edge fade. A mask, not an overlaid gradient: the cards dissolve
       whatever sits behind the section — a white card, the page background, or
       the accent band — so it needs no knowledge of the backdrop. It stops at
       55% alpha rather than fully transparent: enough of a soft edge to say
       "there's more", without a whole card looking cut off. Both insets start at
       0 (a hard edge); effects.js raises the side that still has cards beyond
       it, so a fade only ever shows where there is more to scroll to. */
    --pf-fade: 2rem;
    --pf-fade-start: 0px;
    --pf-fade-end: 0px;
    -webkit-mask-image: linear-gradient(
        to right,
        rgba(0, 0, 0, 0.55) 0,
        #000 var(--pf-fade-start, 0px),
        #000 calc(100% - var(--pf-fade-end, 0px)),
        rgba(0, 0, 0, 0.55) 100%
    );
    mask-image: linear-gradient(
        to right,
        rgba(0, 0, 0, 0.55) 0,
        #000 var(--pf-fade-start, 0px),
        #000 calc(100% - var(--pf-fade-end, 0px)),
        rgba(0, 0, 0, 0.55) 100%
    );
}
@media (min-width: 640px) {
    .pf-rail {
        --pf-per-view: 2;     /* two whole cards, never a clipped third */
    }
}
/* Keyboard users get the rail as a focus stop (tabindex on the element), so it
   needs a visible ring — and the auto-scroll pauses while it is focused. */
.pf-rail:focus-visible {
    outline: 2px solid color-mix(in srgb, var(--cv-primary-colour, #6366f1) 60%, transparent);
    outline-offset: 3px;
    border-radius: 0.25rem;
}
.pf-rail::-webkit-scrollbar {
    display: none;         /* WebKit/Blink: hide the rail's scrollbar entirely */
    width: 0;
    height: 0;
}
/* Reusable "scrolls, but shows no scrollbar" helper — used by the builder-canvas
   portfolio preview so its mini-rail matches the clean public rail. */
.no-scrollbar {
    scrollbar-width: none;
    -ms-overflow-style: none;
}
.no-scrollbar::-webkit-scrollbar {
    display: none;
    width: 0;
    height: 0;
}
.pf-rail > .pf-card {
    /* n cards + (n-1) gaps == the rail's content width, so the visible cards are
       always whole. ``100%`` here is the flex container's content box, which
       already excludes the rail's own padding. */
    flex: 0 0 calc(
        (100% - (var(--pf-per-view) - 1) * var(--pf-gap)) / var(--pf-per-view)
    );
    scroll-snap-align: start;
}

/* Pagination dots under a scrolling rail — one per card. Primary-tinted so they
   read as part of the CV's palette; the active dot stretches into a pill. */
.pf-railwrap {
    position: relative;
}
.pf-dots {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    margin-top: 0.85rem;
}
.pf-dot {
    width: 0.4rem;
    height: 0.4rem;
    padding: 0;
    border: 0;
    border-radius: 999px;
    cursor: pointer;
    background: color-mix(in srgb, var(--cv-primary-colour, #6366f1) 26%, transparent);
    transition: width 0.25s ease, background-color 0.25s ease;
}
.pf-dot:hover {
    background: color-mix(in srgb, var(--cv-primary-colour, #6366f1) 45%, transparent);
}
.pf-dot.is-active {
    width: 1.35rem;
    background: var(--cv-primary-colour, #6366f1);
}
@media (prefers-reduced-motion: reduce) {
    .pf-dot {
        transition: none;
    }
}

.pf-card {
    position: relative;
    display: flex;
    flex-direction: column;
    min-width: 0;
    border-radius: 0.875rem;
    overflow: hidden;
    background: var(--cv-card-bg, #ffffff);
    border: 1px solid var(--cv-card-border, #e5e7eb);
    box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05);
    transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}

/* Square-corners option (section corner_style = "square"). The card clips its
   media with overflow:hidden, so zeroing the card radius squares the whole
   card — media, body and the hover hairline — in one rule. */
.pf-cards--square .pf-card {
    border-radius: 0;
}

/* Hover: a clean outline in the CV's secondary colour — the same "ring on hover"
   gesture as the resume cards on the dashboard, but in the resume's own palette.
   No lift, so a row of project cards stays visually settled.

   Draw the ring in an absolutely positioned overlay *above* the media. A card
   inset shadow is painted below child backgrounds, so the opaque placeholder
   media covered its top and side segments and looked like it clipped outside
   the border. This overlay still paints entirely inside the card — keeping the
   first/last rail cards safe from scrollport clipping — but remains visible over
   images, embeds, and placeholders. */
.pf-card::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 5;
    border: 2px solid transparent;
    border-radius: inherit;
    pointer-events: none;
    transition: border-color 0.2s ease;
}
.pf-card:hover {
    border-color: var(--cv-secondary-colour, #a5b4fc);
}
.pf-card:hover::before {
    border-color: var(--cv-secondary-colour, #a5b4fc);
}

/* ── Media slot: image, vetted video embed, or empty frame ── */
.pf-media {
    position: relative;
    aspect-ratio: 16 / 9;
    flex-shrink: 0;
    overflow: hidden;
    background: color-mix(in srgb, var(--cv-primary-colour, #6366f1) 8%, var(--cv-card-bg, #fff));
    border-bottom: 1px solid var(--cv-card-border, #e5e7eb);
}

.pf-media-img,
.pf-media-frame {
    display: block;
    width: 100%;
    height: 100%;
    border: 0;
}
.pf-media-img {
    object-fit: cover;
    transition: transform 0.4s ease;
}
.pf-card:hover .pf-media-img {
    transform: scale(1.045);
}

.pf-media--zoom {
    cursor: zoom-in;
}

/* Shine sweep across the media well on hover — a light bar travelling over the
   thumbnail, the same gesture as the profile banner's sweep so the two read as
   one visual language. Pointer-events off and stacked under the index/zoom
   badges (z-index 2), so it never intercepts the click-to-enlarge. */
.pf-media::after {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: -70%;
    width: 45%;
    z-index: 1;
    background: linear-gradient(
        105deg,
        transparent 0%,
        rgba(255, 255, 255, 0.1) 32%,
        rgba(255, 255, 255, 0.42) 50%,
        rgba(255, 255, 255, 0.1) 68%,
        transparent 100%
    );
    transform: skewX(-18deg);
    opacity: 0;
    pointer-events: none;
}
.pf-card:hover .pf-media::after {
    animation: pf-media-shine 0.9s ease-out;
}

@keyframes pf-media-shine {
    0%   { left: -70%; opacity: 0; }
    12%  { opacity: 1; }
    88%  { opacity: 1; }
    100% { left: 130%; opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
    .pf-card:hover .pf-media::after {
        animation: none;
    }
}

/* Monospace running index — reads as a numbered project sheet. */
.pf-index {
    position: absolute;
    top: 0.5rem;
    left: 0.5rem;
    z-index: 2;
    font-family: var(--cv-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
    font-size: 0.625rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    line-height: 1.5;
    padding: 0.05rem 0.375rem;
    border-radius: 0.3rem;
    color: #ffffff;
    background: rgba(2, 6, 23, 0.6);
    -webkit-backdrop-filter: blur(4px);
    backdrop-filter: blur(4px);
}

.pf-media-zoom {
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 1.5rem;
    height: 1.5rem;
    border-radius: 0.375rem;
    color: #ffffff;
    background: rgba(2, 6, 23, 0.55);
    -webkit-backdrop-filter: blur(4px);
    backdrop-filter: blur(4px);
    opacity: 0;
    transform: translateY(-2px);
    transition: opacity 0.18s ease, transform 0.18s ease;
    pointer-events: none;
}
.pf-media-zoom svg {
    width: 0.875rem;
    height: 0.875rem;
}
.pf-media--zoom:hover .pf-media-zoom {
    opacity: 1;
    transform: none;
}

/* Empty state: a hatched frame, so an unfilled slot looks deliberate. */
.pf-media--empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.3rem;
    background-image: repeating-linear-gradient(
        -45deg,
        color-mix(in srgb, var(--cv-primary-colour, #6366f1) 9%, transparent) 0 6px,
        transparent 6px 13px
    );
    background-color: color-mix(in srgb, var(--cv-primary-colour, #6366f1) 6%, var(--cv-card-bg, #fff));
    color: color-mix(in srgb, var(--cv-primary-colour, #6366f1) 72%, var(--cv-text-colour, #111827));
}
.pf-media-glyph {
    width: 1.5rem;
    height: 1.5rem;
    opacity: 0.85;
}
.pf-media-hint {
    font-size: 0.625rem;
    font-weight: 600;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    opacity: 0.9;
}

/* Video stand-in for the builder preview: a play glyph on a primary-tinted well
   instead of a live iframe. Same palette as the empty frame, minus the hatching,
   so "there is a video here" reads differently from "there is nothing here". */
.pf-media--video {
    display: flex;
    align-items: center;
    justify-content: center;
    background: color-mix(in srgb, var(--cv-primary-colour, #6366f1) 16%, var(--cv-card-bg, #fff));
    color: color-mix(in srgb, var(--cv-primary-colour, #6366f1) 78%, var(--cv-text-colour, #111827));
}

/* ── Compact variant, used by the builder canvas preview ──
   Three cards to a rail and a step down in type, so a project card still reads
   as a project card inside a section card a third of the page wide. */
.pf-mini {
    --pf-gap: 0.5rem;
}
.pf-mini.pf-rail {
    --pf-per-view: 2.15;
    padding-bottom: 0.25rem;
}
@media (min-width: 768px) {
    .pf-mini.pf-rail {
        --pf-per-view: 2.6;
    }
}
.pf-mini.pf-grid {
    grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr));
    gap: 0.5rem;
}
.pf-mini .pf-card {
    border-radius: 0.6rem;
}
.pf-mini.pf-cards--square .pf-card {
    border-radius: 0;
}
.pf-mini .pf-body {
    gap: 0.25rem;
    padding: 0.5rem 0.55rem 0.55rem;
}
.pf-mini .pf-title {
    font-size: 0.6875rem;
    line-height: 1.25;
}
.pf-mini .pf-desc {
    font-size: 0.625rem;
    line-height: 1.45;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    overflow: hidden;
}
.pf-mini .pf-tag {
    font-size: 0.5625rem;
    line-height: 1.6;
}
.pf-mini .pf-index {
    top: 0.25rem;
    left: 0.25rem;
    font-size: 0.5rem;
    padding: 0 0.25rem;
}
.pf-mini .pf-media-glyph {
    width: 1.1rem;
    height: 1.1rem;
}
.pf-mini .pf-media-hint {
    display: none;
}
.pf-mini .pf-actions {
    gap: 0.25rem;
    padding-top: 0.35rem;
}
.pf-mini .pf-btn {
    font-size: 0.5625rem;
    padding: 0.15rem 0.35rem;
    max-width: 7rem;
}
.pf-mini .pf-btn svg {
    width: 0.6875rem;
    height: 0.6875rem;
}
/* The preview is not the thing itself — no shine sweep, no image zoom. */
.pf-mini .pf-card:hover .pf-media::after {
    animation: none;
}
.pf-mini .pf-card:hover .pf-media-img {
    transform: none;
}

/* ── Body ── */
.pf-body {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    flex: 1 1 auto;
    min-width: 0;
    padding: 0.8rem 0.875rem 0.875rem;
}

.pf-title {
    font-family: var(--cv-font-heading, inherit);
    font-size: 0.9375rem;
    font-weight: 700;
    line-height: 1.3;
    letter-spacing: -0.01em;
    color: var(--cv-text-colour, #111827);
}

.pf-desc {
    font-size: 0.8125rem;
    line-height: 1.55;
    color: var(--cv-muted-colour, #6b7280);
}

/* Tech chips */
.pf-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem;
    margin: 0.1rem 0 0;
    padding: 0;
    list-style: none;
}
.pf-tag {
    font-family: var(--cv-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
    font-size: 0.625rem;
    font-weight: 500;
    line-height: 1.7;
    white-space: nowrap;
    padding: 0.05rem 0.4rem;
    border-radius: 0.3rem;
    color: color-mix(in srgb, var(--cv-primary-colour, #6366f1) 85%, var(--cv-text-colour, #111827));
    background: color-mix(in srgb, var(--cv-primary-colour, #6366f1) 11%, var(--cv-card-bg, #fff));
    border: 1px solid color-mix(in srgb, var(--cv-primary-colour, #6366f1) 22%, transparent);
}

/* ── Action buttons, pinned to the bottom of the card ── */
.pf-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin-top: auto;
    padding-top: 0.625rem;
    border-top: 1px solid color-mix(in srgb, var(--cv-card-border, #e5e7eb) 75%, transparent);
}

.pf-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    min-width: 0;
    padding: 0.32rem 0.6rem;
    border-radius: 0.5rem;
    border: 1px solid transparent;
    font-size: 0.75rem;
    font-weight: 600;
    line-height: 1.35;
    text-decoration: none;
    transition: background 0.16s ease, border-color 0.16s ease, color 0.16s ease;
}
.pf-btn svg {
    width: 0.875rem;
    height: 0.875rem;
    flex-shrink: 0;
}
.pf-btn-text {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.pf-btn--primary {
    background: color-mix(in srgb, var(--cv-primary-colour, #6366f1) 18%, var(--cv-card-bg, #fff));
    border-color: color-mix(in srgb, var(--cv-primary-colour, #6366f1) 42%, transparent);
    color: color-mix(in srgb, var(--cv-primary-colour, #6366f1) 42%, var(--cv-text-colour, #111827));
}
.pf-btn--primary:hover {
    background: color-mix(in srgb, var(--cv-primary-colour, #6366f1) 28%, var(--cv-card-bg, #fff));
    border-color: color-mix(in srgb, var(--cv-primary-colour, #6366f1) 62%, transparent);
}

.pf-btn--secondary {
    background: transparent;
    border-color: color-mix(in srgb, var(--cv-card-border, #e5e7eb) 90%, var(--cv-primary-colour, #6366f1));
    color: var(--cv-muted-colour, #6b7280);
}
.pf-btn--secondary:hover {
    background: color-mix(in srgb, var(--cv-primary-colour, #6366f1) 10%, transparent);
    border-color: color-mix(in srgb, var(--cv-primary-colour, #6366f1) 42%, transparent);
    color: color-mix(in srgb, var(--cv-primary-colour, #6366f1) 42%, var(--cv-text-colour, #111827));
}

/* Sample buttons: template scaffold with no link yet. Non-interactive, so a
   published-too-early CV shows the shape of the button rather than a dead link. */
.pf-btn--sample {
    cursor: default;
    opacity: 0.75;
    border-style: dashed;
}

/* ── Portfolio image lightbox ── */
.pf-lightbox {
    position: fixed;
    inset: 0;
    z-index: 9999;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.875rem;
    background: rgba(0, 0, 0, 0.88);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.18s ease;
    cursor: zoom-out;
    padding: 1.5rem;
}

.pf-lightbox.open {
    opacity: 1;
    pointer-events: auto;
}

.pf-lightbox-img {
    max-width: 92vw;
    max-height: 80vh;
    object-fit: contain;
    border-radius: 0.625rem;
    box-shadow: 0 24px 64px rgba(0, 0, 0, 0.55);
    display: block;
    transform: scale(0.95);
    transition: transform 0.22s ease;
}

.pf-lightbox.open .pf-lightbox-img {
    transform: scale(1);
}

.pf-lightbox-caption {
    font-size: 0.875rem;
    color: rgba(255, 255, 255, 0.85);
    text-align: center;
    max-width: 640px;
    line-height: 1.5;
}

.pf-lightbox-close {
    position: absolute;
    top: 0.875rem;
    right: 0.875rem;
    width: 32px;
    height: 32px;
    border-radius: 9999px;
    background: rgba(255, 255, 255, 0.15);
    border: 1px solid rgba(255, 255, 255, 0.2);
    color: #fff;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.15s ease;
}

.pf-lightbox-close:hover {
    background: rgba(255, 255, 255, 0.22);
}

/* ══════════════════════════════════════════════════════════════
   Certification thumbnails
   ══════════════════════════════════════════════════════════════ */
.cert-thumb {
    width: 48px;
    height: 48px;
    border-radius: 0.625rem;
    object-fit: cover;
    flex-shrink: 0;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
    border: 1px solid rgba(255, 255, 255, 0.08);
    transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.cert-thumb:hover {
    transform: scale(1.07);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
}

.cert-view-link {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    font-size: 0.6875rem;
    font-weight: 500;
    padding: 0.2rem 0.55rem;
    border-radius: 9999px;
    background: color-mix(in srgb, var(--cv-primary-colour, #6366f1) 12%, transparent);
    color: color-mix(in srgb, var(--cv-primary-colour, #6366f1) 90%, var(--cv-text-colour, #111827));
    border: 1px solid color-mix(in srgb, var(--cv-primary-colour, #6366f1) 22%, transparent);
    text-decoration: none;
    transition: background 0.15s ease, border-color 0.15s ease;
}

.cert-view-link:hover {
    background: color-mix(in srgb, var(--cv-primary-colour, #6366f1) 22%, transparent);
    border-color: color-mix(in srgb, var(--cv-primary-colour, #6366f1) 40%, transparent);
}

/* ══════════════════════════════════════════════════════════════
   Certification lightbox — click-to-enlarge overlay
   ══════════════════════════════════════════════════════════════ */
.cert-lightbox {
    position: fixed;
    inset: 0;
    z-index: 9999;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.82);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.18s ease;
    cursor: pointer;
}

.cert-lightbox.open {
    opacity: 1;
    pointer-events: auto;
}

.cert-lightbox-backdrop {
    display: none;
}

.cert-lightbox-panel {
    position: relative;
    z-index: 1;
    max-width: min(92vw, 660px);
    background: var(--cv-card-bg, #fff);
    border-radius: 1rem;
    overflow: hidden;
    box-shadow: 0 24px 64px rgba(0, 0, 0, 0.45);
    transform: scale(0.94);
    transition: transform 0.22s ease;
}

.cert-lightbox.open .cert-lightbox-panel {
    transform: scale(1);
}

.cert-lightbox-img {
    display: block;
    width: 100%;
    max-height: 78vh;
    object-fit: contain;
    background: #000;
}

.cert-lightbox-info {
    padding: 0.75rem 1rem 0.875rem;
    border-top: 1px solid var(--cv-card-border, #e5e7eb);
}

.cert-lightbox-name {
    font-size: 0.875rem;
    font-weight: 600;
    color: var(--cv-text-colour, #111827);
    line-height: 1.35;
}

.cert-lightbox-meta {
    font-size: 0.75rem;
    color: var(--cv-muted-colour, #6b7280);
    margin-top: 0.2rem;
}

.cert-lightbox-close {
    position: absolute;
    top: 0.6rem;
    right: 0.6rem;
    width: 30px;
    height: 30px;
    border-radius: 9999px;
    background: rgba(0, 0, 0, 0.5);
    color: #fff;
    border: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.15s ease;
    z-index: 2;
}

.cert-lightbox-close:hover {
    background: rgba(0, 0, 0, 0.75);
}

.cert-thumb-clickable {
    cursor: zoom-in;
}


/* ══════════════════════════════════════════════════════════════
   Per-template fonts (public CV render / preview)
   --cv-font-* default to the Inter stack, so CVs created before the
   templates feature — which carry no font_theme — look identical.
   Scoped to .cv-fonts (public render root only) so the builder chrome
   is never restyled.
   ══════════════════════════════════════════════════════════════ */
.cv-fonts {
    font-family: var(--cv-font-body, "Inter", system-ui, -apple-system, sans-serif);
}
/* Every heading level takes the display face, not just the section labels — so
   the chosen pairing reaches entry titles too (job titles, degrees, project and
   certification names), and the font choice reads as applied to the whole page
   rather than a couple of labels. Body text keeps the body face above. */
.cv-fonts h1,
.cv-fonts h2,
.cv-fonts h3,
.cv-fonts h4,
.cv-fonts h5,
.cv-fonts h6 {
    font-family: var(--cv-font-heading, "Inter", system-ui, -apple-system, sans-serif);
}

/* Tech theme flourish: code-style monospace section labels (the small
   uppercase section titles), for a distinctly technical feel. Placed after
   the rule above so it wins for tech CVs; the name (h1) keeps the display
   heading font. */
.cv-font-tech h2 {
    font-family: var(--cv-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
    letter-spacing: 0.12em;
}

/* ══════════════════════════════════════════════════════════════
   Section title marker (CV-wide "Title mark" setting)
   ──────────────────────────────────────────────────────────────
   A small primary-coloured shape before each section heading. On a
   template with borderless sections there is no card edge to anchor
   a heading, so the marker does that job. Deliberately static — no
   glow, no animation: it repeats down the whole page, and anything
   that moves or pulses that many times becomes noise.

   The shape class sits on the render root, so one setting styles
   every heading. No class == no marker, which is the default.

   Each shape is listed twice: once for the real section heading and once for
   ``.cv-marker-demo``, the little live sample beside the picker in the builder
   (the builder canvas renders its own card chrome, so there is no real heading
   there to preview against). Same class names drive both, so the geometry is
   still defined once per shape.
   ══════════════════════════════════════════════════════════════ */
.cv-marker-bar h2.cv-section-title,
.cv-marker-dot h2.cv-section-title,
.cv-marker-square h2.cv-section-title,
.cv-marker-diamond h2.cv-section-title {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}
.cv-marker-demo {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 0.875rem;
    height: 0.875rem;
}
.cv-marker-bar h2.cv-section-title::before,
.cv-marker-bar .cv-marker-demo::before,
.cv-marker-dot h2.cv-section-title::before,
.cv-marker-dot .cv-marker-demo::before,
.cv-marker-square h2.cv-section-title::before,
.cv-marker-square .cv-marker-demo::before,
.cv-marker-diamond h2.cv-section-title::before,
.cv-marker-diamond .cv-marker-demo::before {
    content: "";
    flex: 0 0 auto;
    background: var(--cv-primary-colour, #6366f1);
}
.cv-marker-bar h2.cv-section-title::before,
.cv-marker-bar .cv-marker-demo::before {
    width: 0.1875rem;
    height: 0.875rem;
    border-radius: 999px;
}
.cv-marker-dot h2.cv-section-title::before,
.cv-marker-dot .cv-marker-demo::before {
    width: 0.4375rem;
    height: 0.4375rem;
    border-radius: 999px;
}
.cv-marker-square h2.cv-section-title::before,
.cv-marker-square .cv-marker-demo::before {
    width: 0.375rem;
    height: 0.375rem;
    border-radius: 0.0625rem;
}
.cv-marker-diamond h2.cv-section-title::before,
.cv-marker-diamond .cv-marker-demo::before {
    width: 0.375rem;
    height: 0.375rem;
    transform: rotate(45deg);
}

/* ══════════════════════════════════════════════════════════════
   Placeholder image — a theme-tinted "drop an image here" box.
   Inherits --cv-primary-colour so it reads as intentional in both the
   light and dark variant of a template. Used by {% cv_img_placeholder %}.
   ══════════════════════════════════════════════════════════════ */
.cv-img-ph {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    width: 100%;
    padding: 1rem;
    border-radius: 0.6rem;
    text-align: center;
    background: color-mix(in srgb, var(--cv-primary-colour, #6366f1) 9%, var(--cv-card-bg, #fff));
    border: 1.5px dashed color-mix(in srgb, var(--cv-primary-colour, #6366f1) 42%, transparent);
    color: color-mix(in srgb, var(--cv-primary-colour, #6366f1) 78%, var(--cv-text-colour, #111827));
}
.cv-img-ph-glyph {
    width: 1.6rem;
    height: 1.6rem;
    opacity: 0.9;
}
.cv-img-ph-caption {
    font-size: 0.75rem;
    font-weight: 600;
    line-height: 1.35;
    max-width: 22rem;
}
.cv-img-ph-ratio {
    position: absolute;
    top: 0.4rem;
    right: 0.45rem;
    font-size: 0.625rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    padding: 0.08rem 0.34rem;
    border-radius: 0.3rem;
    background: color-mix(in srgb, var(--cv-primary-colour, #6366f1) 16%, transparent);
}
/* Compact variant for small inline slots (e.g. a certificate thumbnail). */
.cv-img-ph--compact {
    padding: 0.3rem;
    gap: 0;
    border-radius: 0.45rem;
}
.cv-img-ph--compact .cv-img-ph-glyph {
    width: 1.1rem;
    height: 1.1rem;
}

/* The "Sample" badge and its companion note are gone: the seeded copy reads as
   prompt text on its own ("Your job title", "20XX"), so an amber flag on every
   untouched section was noise in the chrome. The pre-publish confirmation still
   counts unedited sections — one warning, at the moment it matters. */


/* ══════════════════════════════════════════════════════════════
   Template picker modal (dashboard "Create a new stack")
   ──────────────────────────────────────────────────────────────
   A visual picker: each card leads with a live mini-preview of the
   template's own palette (banner, avatar, accent band, skeleton
   name/tagline + skill chips), so the choice reads at a glance rather
   than from an icon and two dots. A usable card is a <form> with a
   transparent full-card submit button behind the content; the
   Light/Dark toggle sits above it (pointer-events on) and repaints the
   preview live via the :has() rule below. Premium cards link to
   checkout; coming-soon cards are inert. Fixed light chrome — this is
   app UI, never a CV's palette.
   ══════════════════════════════════════════════════════════════ */
.tp-backdrop {
    position: fixed;
    inset: 0;
    z-index: 9999;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
    background: rgba(3, 7, 18, 0.68);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s ease;
}
.tp-backdrop.open {
    opacity: 1;
    pointer-events: auto;
}
.tp-modal {
    display: flex;
    flex-direction: column;
    width: 100%;
    max-width: 60rem;
    max-height: 90vh;
    overflow: hidden;                 /* header + footer stay put; only the grid scrolls */
    background: #fff;
    border-radius: 1.5rem;
    /* Flat: the dimmed backdrop separates it; an amber cap marks the top. */
    border-top: 4px solid #fbcb2b;
    transform: translateY(12px) scale(0.985);
    transition: transform 0.22s cubic-bezier(0.16, 1, 0.3, 1);
}
.tp-backdrop.open .tp-modal {
    transform: none;
}
.tp-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    padding: 1.4rem 1.5rem 1.1rem;
    border-bottom: 1px solid #e3e7ef;
}
.tp-eyebrow {
    font-size: 0.625rem;
    font-weight: 800;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: #2451f0;
}
.tp-title {
    margin-top: 0.15rem;
    font-size: 1.3rem;
    font-weight: 800;
    letter-spacing: -0.02em;
    line-height: 1.1;
    color: #0b1220;
}
.tp-subtitle {
    margin-top: 0.28rem;
    font-size: 0.8rem;
    color: #6b7280;
}
.tp-close {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: 0.75rem;
    color: #6b7280;
    background: #f3f4f6;
    transition: background 0.15s, color 0.15s;
}
.tp-close:hover { background: #e5e7eb; color: #111827; }
.tp-close:focus-visible { outline: 3px solid #fadb5f; outline-offset: 2px; }
.tp-close svg { width: 1.15rem; height: 1.15rem; }
.tp-grid {
    display: grid;
    gap: 0.85rem;
    padding: 1.25rem 1.5rem;
    overflow-y: auto;                 /* the only scroll region in the modal */
    grid-template-columns: repeat(1, minmax(0, 1fr));
}
@media (min-width: 640px) {
    .tp-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1024px) {
    .tp-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* ── Card shell ── */
.tp-card {
    position: relative;
    display: flex;
    flex-direction: column;
    border: 1.5px solid #d6dce8;
    border-radius: 1rem;
    padding: 0.5rem 0.5rem 0.85rem;
    background: #fff;
    overflow: hidden;
    text-decoration: none;
    transition: border-color 0.16s, transform 0.25s cubic-bezier(0.22, 1, 0.36, 1);
}
form.tp-card,
a.tp-card { cursor: pointer; }
/* Hover: rise + a coloured edge (brand blue to use, amber to unlock). */
form.tp-card:hover,
form.tp-card:focus-within {
    border-color: #2451f0;
    transform: translateY(-3px);
}
a.tp-card--locked:hover,
a.tp-card--locked:focus-visible {
    border-color: #f5b70a;
    transform: translateY(-3px);
}
a.tp-card--locked:focus-visible { outline: none; }
@media (prefers-reduced-motion: reduce) {
    form.tp-card:hover, form.tp-card:focus-within,
    a.tp-card--locked:hover, a.tp-card--locked:focus-visible { transform: none; }
}

/* Transparent full-card submit button. Content above sets pointer-events:none
   so clicks fall through to this; only the theme toggle re-enables them. */
.tp-hit {
    position: absolute;
    inset: 0;
    z-index: 1;
    width: 100%;
    height: 100%;
    margin: 0; padding: 0; border: 0;
    background: transparent;
    cursor: pointer;
    border-radius: inherit;
}
.tp-hit:focus-visible {
    outline: 2px solid #2451f0;
    outline-offset: -3px;
}

/* ── Mini preview ── the palette as a tiny resume. Private --_* vars resolve to
   the light values by default and are re-pointed to the dark set by the single
   :has() rule below, so every layer switches together. */
.tp-thumb {
    --_bg: var(--tp-bg, #f4f4f5);
    --_pri: var(--tp-pri, #2451f0);
    --_sec: var(--tp-sec, #7c3aed);
    --_band: var(--tp-band, #e0e7ff);
    /* Skeleton "text" takes the template's own ink where it sets one, so warm
       paper stock doesn't get cool navy lines. --_ink2 is derived, so the dark
       rule below only has to re-point --_ink. */
    --_ink: var(--tp-ink, rgba(15, 23, 42, 0.78));
    --_ink2: color-mix(in srgb, var(--_ink) 42%, var(--_bg));
    position: relative;
    z-index: 2;
    pointer-events: none;
    height: 8.5rem;
    border-radius: 0.7rem;
    overflow: hidden;
    background: var(--_bg);
    box-shadow: inset 0 0 0 1px rgba(15, 23, 42, 0.05);
}
.tp-thumb-band {
    position: absolute;
    top: 0; right: 0; bottom: 0;
    width: 26%;
    background: color-mix(in srgb, var(--_band) 60%, var(--_bg));
}
.tp-thumb-banner {
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 38%;
    background: linear-gradient(115deg, var(--_pri), var(--_sec));
}
.tp-thumb-avatar {
    position: absolute;
    top: 25%;
    left: 9%;
    width: 2.1rem;
    height: 2.1rem;
    border-radius: 999px;
    background: linear-gradient(135deg, var(--_pri), var(--_sec));
    box-shadow: 0 0 0 3px var(--_bg);
}
.tp-thumb-body {
    position: absolute;
    left: 9%;
    right: 30%;
    top: 58%;
    display: flex;
    flex-direction: column;
    gap: 0.34rem;
}
/* No accent band (single-column templates) → the skeleton text runs the full
   width of the sheet, as it does on the real page, and the banner drops to a
   masthead strip over a hairline rule. A full-height colour banner on a
   print-style template was the loudest thing in the grid while advertising a
   header the template doesn't actually ship. */
.tp-thumb:not(:has(.tp-thumb-band)) .tp-thumb-body {
    right: 12%;
    top: 46%;
}
.tp-thumb:not(:has(.tp-thumb-band)) .tp-thumb-banner {
    height: 22%;
    border-bottom: 1px solid color-mix(in srgb, var(--_pri) 45%, var(--_bg));
}
.tp-thumb:not(:has(.tp-thumb-band)) .tp-thumb-avatar {
    top: 13%;
}
.tp-thumb-line {
    height: 0.42rem;
    border-radius: 999px;
    background: var(--_ink);
}
.tp-thumb-line.tp-l1 { width: 70%; }
.tp-thumb-line.tp-l2 { width: 46%; background: var(--_ink2); }
.tp-thumb-chips {
    display: flex;
    gap: 0.28rem;
    margin-top: 0.18rem;
}
.tp-thumb-chips i {
    width: 1.5rem;
    height: 0.5rem;
    border-radius: 999px;
    display: block;
}
.tp-thumb-chips i:nth-child(1) { background: color-mix(in srgb, var(--_pri) 32%, var(--_bg)); }
.tp-thumb-chips i:nth-child(2) { background: color-mix(in srgb, var(--_sec) 36%, var(--_bg)); }
.tp-thumb-chips i:nth-child(3) { background: color-mix(in srgb, var(--_pri) 20%, var(--_bg)); }

/* Dark toggle → re-point the private vars to the template's dark palette. */
.tp-card:has(.tp-seg input[value="dark"]:checked) .tp-thumb {
    --_bg: var(--tp-bg-d, #0b1020);
    --_pri: var(--tp-pri-d, #a5b4fc);
    --_sec: var(--tp-sec-d, #22d3ee);
    --_band: var(--tp-band-d, #1e1b4b);
    --_ink: var(--tp-ink-d, rgba(255, 255, 255, 0.86));
}

/* ── Card text ── */
.tp-info {
    position: relative;
    z-index: 2;
    pointer-events: none;
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    padding: 0.75rem 0.4rem 0;
}
.tp-name {
    font-size: 0.9rem;
    font-weight: 800;
    line-height: 1.2;
    color: #0b1220;
}
.tp-tag {
    margin-top: 0.15rem;
    font-size: 0.72rem;
    line-height: 1.4;
    color: #6b7280;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    overflow: hidden;
}
.tp-foot {
    position: relative;
    z-index: 2;
    pointer-events: none;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    margin-top: auto;
    padding: 0.85rem 0.4rem 0;
}
.tp-foot-note {
    font-size: 0.7rem;
    font-weight: 700;
    color: #b45309;
}
/* Darker than it looks it should be: the card sits at 0.55 opacity, so a grey-400
   note would fade to unreadable. */
.tp-foot-note--soon { color: #6b7280; }
.tp-use {
    display: inline-flex;
    align-items: center;
    gap: 0.28rem;
    font-size: 0.72rem;
    font-weight: 800;
    color: #2451f0;
    opacity: 0;
    transform: translateX(-4px);
    transition: opacity 0.16s, transform 0.16s;
}
.tp-use svg { width: 0.85rem; height: 0.85rem; }
form.tp-card:hover .tp-use,
form.tp-card:focus-within .tp-use,
a.tp-card--locked:hover .tp-use,
a.tp-card--locked:focus-visible .tp-use { opacity: 1; transform: none; }
a.tp-card--locked .tp-use { color: #b45309; }

/* ── Theme toggle (light/dark): the one interactive bit above the hit btn ── */
.tp-seg {
    position: relative;
    z-index: 3;
    pointer-events: auto;
    display: inline-flex;
    padding: 2px;
    border-radius: 999px;
    background: #f3f4f6;
    border: 1px solid #e5e7eb;
}
.tp-seg label { position: relative; cursor: pointer; display: block; }
.tp-seg input { position: absolute; opacity: 0; width: 0; height: 0; }
.tp-seg span {
    display: block;
    padding: 0.18rem 0.66rem;
    border-radius: 999px;
    font-size: 0.68rem;
    font-weight: 700;
    color: #6b7280;
    transition: background 0.15s, color 0.15s;
}
.tp-seg input:checked + span { background: #0b1220; color: #fff; }
.tp-seg input:focus-visible + span { outline: 2px solid #2451f0; outline-offset: 1px; }

/* ── Status flags, sitting over the preview ── */
.tp-flag {
    position: absolute;
    top: 0.75rem;
    left: 0.75rem;
    z-index: 3;
    pointer-events: none;
    display: inline-flex;
    align-items: center;
    gap: 0.24rem;
    font-size: 0.6rem;
    font-weight: 800;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    padding: 0.2rem 0.5rem;
    border-radius: 999px;
    -webkit-backdrop-filter: blur(4px);
    backdrop-filter: blur(4px);
}
.tp-flag svg { width: 0.7rem; height: 0.7rem; }
.tp-flag--premium {
    color: #0b1220;
    background: #fbcb2b;
    border: 1px solid #0b1220;
}

/* ── Unavailable templates ──
   No badge: the card state carries the message. Fully desaturated, faded and
   set on a recessed background with a dashed edge, so the two live templates
   are the only things in the grid that look pickable. Text is dimmed on top of
   the card-level opacity, which keeps the name readable enough to scan the
   roadmap without competing with a real option. */
.tp-card--soon {
    cursor: default;
    opacity: 0.55;
    filter: grayscale(1);
    background: #fafbfc;
    border-color: #e8ebf1;
    border-style: dashed;
    box-shadow: none;
}
.tp-card--soon .tp-thumb {
    box-shadow: inset 0 0 0 1px rgba(15, 23, 42, 0.04);
}
.tp-card--soon .tp-name { color: #4b5563; }
.tp-card--soon .tp-tag { color: #9ca3af; }
.tp-card--soon:hover {
    /* No hover affordance whatsoever — it isn't clickable. */
    opacity: 0.55;
    border-color: #e8ebf1;
    box-shadow: none;
    transform: none;
}

/* ── Blank / copy fallback footer ── */
.tp-blank {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    padding: 1rem 1.5rem;
    border-top: 1px solid #e3e7ef;
    background: #f5f7fb;
}
.tp-blank-form {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.tp-blank-input,
.tp-blank-select {
    border: 1px solid #e5e7eb;
    border-radius: 0.6rem;
    padding: 0.5rem 0.7rem;
    font-size: 0.8rem;
    background: #fff;
    color: #111827;
}
.tp-blank-input:focus,
.tp-blank-select:focus {
    outline: none;
    border-color: #2451f0;
    box-shadow: 0 0 0 3px rgba(36, 81, 240, 0.13);
}
.tp-blank-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    white-space: nowrap;
    padding: 0.5rem 1.1rem;
    border-radius: 0.6rem;
    font-size: 0.8rem;
    font-weight: 700;
    color: #374151;
    background: #fff;
    border: 1px solid #d8dde7;
    transition: background 0.15s, border-color 0.15s, color 0.15s;
}
.tp-blank-btn:hover { background: #f3f4f6; color: #0b1220; border-color: #c3cad6; }
.tp-blank-title { font-size: 0.85rem; font-weight: 800; color: #0b1220; }
.tp-blank-sub { font-size: 0.72rem; color: #6b7280; margin-top: 0.1rem; }
@media (min-width: 640px) {
    .tp-blank { flex-direction: row; align-items: center; justify-content: space-between; }
    .tp-blank-form { flex-direction: row; align-items: center; }
    .tp-blank-input { width: 11rem; }
}
@media (prefers-reduced-motion: reduce) {
    .tp-modal, .tp-card, .tp-use { transition: none; }
}


/* ══════════════════════════════════════════════════════════════
   Text-section typography (professional summary / text block)
   ──────────────────────────────────────────────────────────────
   Emitted by {% section_text_classes %}. Deliberately hand-authored
   rather than Tailwind utilities: static/css/tailwind.css is a
   prebuilt artefact, so a fresh utility class wouldn't exist without
   a CSS rebuild, and owning the rules lets line-height tighten as the
   size grows — a 21px paragraph at 1.65 leading reads as too airy.

   Sizes are rem so they're independent of the parent; the mono
   variant is em so it composes with whichever size is active.
   ══════════════════════════════════════════════════════════════ */
.cv-text {
    font-size: 0.875rem;   /* == cv-text--sm, the default */
    line-height: 1.65;
}
.cv-text--xs {
    font-size: 0.75rem;
    line-height: 1.7;
}
.cv-text--sm {
    font-size: 0.875rem;
    line-height: 1.65;
}
.cv-text--base {
    font-size: 1rem;
    line-height: 1.62;
}
.cv-text--lg {
    font-size: 1.125rem;
    line-height: 1.58;
}
.cv-text--xl {
    font-size: 1.3125rem;
    line-height: 1.5;
    letter-spacing: -0.011em;
}

/* Font role. Falls back to the Inter stack when a CV has no font_theme, which
   is every CV created before the template presets landed. */
.cv-font--heading {
    font-family: var(--cv-font-heading, "Inter", system-ui, -apple-system, sans-serif);
    letter-spacing: -0.012em;
}
.cv-font--mono {
    font-family: var(--cv-font-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace);
    /* Monospace runs optically larger at the same px, so trim it back and give
       the longer lines a touch more air. */
    font-size: 0.94em;
    line-height: 1.72;
    letter-spacing: -0.005em;
}


/* ══════════════════════════════════════════════════════════════
   Builder edit panel — text controls
   Fixed light-theme colours (the panel is app chrome, not CV
   content), so these never inherit a dark CV palette.
   ══════════════════════════════════════════════════════════════ */

/* Shared segmented control: a hairline-divided row of radio labels. */
.cv-seg-row {
    display: flex;
    align-items: stretch;
    overflow: hidden;
    border: 1px solid #e5e7eb;
    border-radius: 0.5rem;
    background: #ffffff;
}
.cv-seg-row > label {
    position: relative;
    flex: 1 1 0;
    min-width: 0;
    cursor: pointer;
}
.cv-seg-row > label + label {
    border-left: 1px solid #e5e7eb;
}
.cv-seg-opt {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.25rem;
    height: 100%;
    padding: 0.35rem 0.25rem;
    font-size: 0.625rem;
    font-weight: 600;
    line-height: 1.2;
    color: #6b7280;
    text-align: center;
    transition: background 0.15s ease, color 0.15s ease;
}
.cv-seg-row > label:hover .cv-seg-opt {
    background: #f9fafb;
    color: #111827;
}
.cv-seg-row input:checked + .cv-seg-opt {
    background: #1f2937;
    color: #ffffff;
}
.cv-seg-row input:focus-visible + .cv-seg-opt {
    outline: 2px solid #2451f0;
    outline-offset: -2px;
}

/* ══════════════════════════════════════════════════════════════
   Theme panel — selects, swatch tooltips, ink mode row
   Fixed light-theme colours: the sidebar is app chrome, not CV
   content, so none of this inherits a dark CV palette.
   ══════════════════════════════════════════════════════════════ */

/* Native select, restyled: hairline field with our own chevron so it matches the
   segmented controls beside it instead of the OS default. */
.cv-panel-select {
    appearance: none;
    -webkit-appearance: none;
    width: 100%;
    padding: 0.35rem 1.65rem 0.35rem 0.55rem;
    border: 1px solid #e5e7eb;
    border-radius: 0.5rem;
    background-color: #ffffff;
    /* Chevron drawn as an inline SVG so it needs no extra asset. */
    background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='%236b7280' stroke-width='1.8'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M6 8l4 4 4-4'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.4rem center;
    background-size: 0.85rem 0.85rem;
    font-size: 0.6875rem;
    font-weight: 600;
    line-height: 1.4;
    color: #1f2937;
    cursor: pointer;
    transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.cv-panel-select:hover {
    border-color: #d1d5db;
}
.cv-panel-select:focus-visible {
    outline: none;
    border-color: #2451f0;
    box-shadow: 0 0 0 3px rgba(36, 81, 240, 0.14);
}

/* ── Font picker: a custom dropdown that renders each option in its own face ──
   A native <select> can't do this (browsers ignore font-family on <option>), so
   this is hand-built: the trigger shows the current pairing, and the menu lists
   every pairing set in its own typeface. Fixed light chrome — it's app UI, not
   CV content, so it never inherits a dark CV palette. */
.cv-fontpick {
    position: relative;
}
.cv-fontpick-trigger {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
    padding: 0.4rem 0.5rem 0.4rem 0.6rem;
    border: 1px solid #e5e7eb;
    border-radius: 0.5rem;
    background: #ffffff;
    cursor: pointer;
    text-align: left;
    transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.cv-fontpick-trigger:hover {
    border-color: #d1d5db;
}
.cv-fontpick[data-open] .cv-fontpick-trigger,
.cv-fontpick-trigger:focus-visible {
    outline: none;
    border-color: #2451f0;
    box-shadow: 0 0 0 3px rgba(36, 81, 240, 0.14);
}
.cv-fontpick-cur {
    flex: 1 1 auto;
    min-width: 0;
    font-size: 0.9rem;
    font-weight: 600;
    line-height: 1.2;
    color: #111827;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.cv-fontpick-note {
    flex-shrink: 0;
    font-size: 0.5625rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #9ca3af;
}
.cv-fontpick-caret {
    flex-shrink: 0;
    width: 0.85rem;
    height: 0.85rem;
    color: #6b7280;
    transition: transform 0.16s ease;
}
.cv-fontpick[data-open] .cv-fontpick-caret {
    transform: rotate(180deg);
}

/* Menu is position:fixed and placed by JS, so the surrounding scroll panels
   never clip it. */
.cv-fontpick-menu {
    position: fixed;
    z-index: 80;
    max-height: 18rem;
    overflow-y: auto;
    padding: 0.3rem;
    background: #ffffff;
    border: 1px solid #e5e7eb;
    border-radius: 0.75rem;
    box-shadow: 0 18px 44px rgba(12, 18, 32, 0.18);
    scrollbar-width: thin;
}
.cv-fontpick-opt {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    width: 100%;
    padding: 0.4rem 0.5rem;
    border-radius: 0.5rem;
    background: transparent;
    cursor: pointer;
    text-align: left;
    transition: background-color 0.1s ease;
}
.cv-fontpick-opt + .cv-fontpick-opt {
    margin-top: 0.1rem;
}
.cv-fontpick-opt:hover {
    background: #f3f4f6;
}
.cv-fontpick-opt.is-active {
    background: #eef4ff;
}
/* Big glyph tile previewing the face. */
.cv-fontpick-opt-face {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    border-radius: 0.45rem;
    background: #f8fafc;
    border: 1px solid #eef2f7;
    font-size: 1.05rem;
    font-weight: 600;
    line-height: 1;
    color: #1f2937;
}
.cv-fontpick-opt.is-active .cv-fontpick-opt-face {
    background: #ffffff;
    border-color: #c7d7fe;
    color: #2451f0;
}
.cv-fontpick-opt-text {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.05rem;
}
.cv-fontpick-opt-name {
    font-size: 0.9rem;
    font-weight: 600;
    line-height: 1.15;
    color: #111827;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.cv-fontpick-opt-note {
    font-size: 0.5625rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #9ca3af;
}
.cv-fontpick-tick {
    flex-shrink: 0;
    width: 0.9rem;
    height: 0.9rem;
    color: #2451f0;
    opacity: 0;
}
.cv-fontpick-opt.is-active .cv-fontpick-tick {
    opacity: 1;
}

/* Five colour swatches across. Hand-authored because the shipped tailwind.css is
   a prebuilt artefact that has grid-cols-4 but not grid-cols-5. */
.cv-swatch-row {
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    gap: 0.25rem;
}

/* Swatch label tooltip — one class in place of the long utility chain that used
   to be repeated on every swatch. */
.cv-swatch-tip {
    position: absolute;
    bottom: calc(100% + 0.35rem);
    left: 50%;
    transform: translateX(-50%);
    z-index: 50;
    padding: 0.1rem 0.35rem;
    border-radius: 0.25rem;
    background: #111827;
    color: #ffffff;
    font-size: 9px;
    line-height: 1.5;
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.12s ease;
}
.group:hover > .cv-swatch-tip {
    opacity: 1;
}

/* Text-colour row: Dark / Light are radios, Custom is a checkbox that overrides
   them server-side. While Custom is on, dim the two presets so exactly one
   segment ever reads as active. */
.cv-ink-row:has(input[type="checkbox"]:checked) input[type="radio"]:checked + .cv-seg-opt {
    background: #ffffff;
    color: #9ca3af;
}

/* ══════════════════════════════════════════════════════════════
   Resume Theme panel — refreshed chrome (header, grouped sections,
   palette well). Fixed light styling: this is builder sidebar UI, so
   it never inherits a CV's (possibly dark) palette.
   ══════════════════════════════════════════════════════════════ */
.cv-theme-head {
    margin-bottom: 0.9rem;
}
.cv-theme-title {
    font-size: 0.8125rem;
    font-weight: 800;
    line-height: 1.1;
    letter-spacing: -0.01em;
    color: #0b1220;
}
.cv-theme-sub {
    font-size: 0.625rem;
    font-weight: 500;
    line-height: 1.2;
    color: #9ca3af;
    margin-top: 0.1rem;
}
.cv-theme-groups {
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
}
/* A hairline between groups replaces the old stack of <div class="h-px"> rules. */
.cv-theme-group + .cv-theme-group {
    padding-top: 0.85rem;
    border-top: 1px solid rgba(15, 23, 42, 0.06);
}
.cv-theme-label {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    margin-bottom: 0.5rem;
    font-size: 0.5625rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: #94a3b8;
}
.cv-theme-label::before {
    content: "";
    flex-shrink: 0;
    width: 0.3rem;
    height: 0.3rem;
    border-radius: 999px;
    background: #c7d2fe;
}
.cv-theme-sublabel {
    font-size: 0.5625rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #94a3b8;
}

/* Palette well: the five colour inputs read as rounded-square chips inside a
   soft inset panel — a "pro tool" swatch tray rather than five loose circles. */
.cv-palette {
    padding: 0.6rem 0.55rem 0.5rem;
    border-radius: 0.7rem;
    background: #f8fafc;
    border: 1px solid #e3e7ef;
}
.cv-palette .color-swatch {
    width: 2rem;
    height: 2rem;
    border-radius: 0.55rem;
    border: 2px solid #ffffff;
    box-shadow: 0 0 0 1px #c9d2e3; /* a hairline ring, no drop */
}
.cv-palette .color-swatch:hover {
    transform: translateY(-1px) scale(1.04);
    border-color: #ffffff;
    box-shadow: 0 0 0 1.5px #2451f0;
}
.cv-palette .color-swatch::-webkit-color-swatch { border-radius: 0.4rem; }
.cv-palette .color-swatch::-moz-color-swatch { border-radius: 0.4rem; }
/* Anchor the first / last swatch tooltips inward so a centered tooltip on an
   edge chip can't poke past the sidebar (which now clips overflow-x). */
.cv-palette > div:first-child .cv-swatch-tip { left: 0; transform: none; }
.cv-palette > div:last-child .cv-swatch-tip { left: auto; right: 0; transform: none; }
.cv-swatch-cap {
    font-size: 0.5rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    line-height: 1;
    color: #94a3b8;
}

/* Settings row (layout toggle): label on the left, control on the right. */
.cv-theme-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.5rem 0.7rem;
    border: 1px solid #e3e7ef;
    border-radius: 0.65rem;
    background: #ffffff;
    transition: border-color 0.12s ease, background 0.12s ease;
}
.cv-theme-row:hover {
    border-color: #2451f0;
    background: #fcfdff;
}
.cv-theme-row-text {
    display: flex;
    flex-direction: column;
    gap: 0.05rem;
    min-width: 0;
}
.cv-theme-row-title {
    font-size: 0.75rem;
    font-weight: 600;
    line-height: 1.15;
    color: #374151;
}
.cv-theme-row-sub {
    font-size: 0.625rem;
    font-weight: 500;
    line-height: 1.15;
    color: #9ca3af;
}
/* Band style / thickness: indented and rail-connected under the accent-band
   toggle so they read as its dependents, not two more standalone rows. Hidden
   entirely (via the [hidden] attribute, toggled in builder.html) while the
   band itself is off. */
.cv-theme-subgroup {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    margin: 0.5rem 0 0 0.55rem;
    padding: 0.65rem 0.7rem 0.7rem 0.7rem;
    border-left: 2px solid #eef2f7;
    border-radius: 0 0.6rem 0.6rem 0;
    background: #fbfcfe;
}
.cv-theme-subgroup[hidden] {
    display: none;
}

/* Size picker: each segment shows an "A" at that step, so the control
   previews what it does instead of naming it. */
.cv-size-a {
    display: block;
    font-weight: 700;
    line-height: 1;
}
.cv-size-a--xs   { font-size: 8px; }
.cv-size-a--sm   { font-size: 10px; }
.cv-size-a--base { font-size: 12px; }
.cv-size-a--lg   { font-size: 14px; }
.cv-size-a--xl   { font-size: 17px; }

/* ══════════════════════════════════════════════════════════════
   GitHub activity terminal
   Compact cards become a contained 1:1 telemetry window. Wider cards retain
   36- and 53-week density while sharing the same animated system language.
   ══════════════════════════════════════════════════════════════ */
.cv-section--github_contributions {
    box-sizing: border-box;
    min-width: 0;
    max-width: 100%;
    overflow: hidden;
}
/* Quarter cards share neighboring section padding, then cap the visual itself.
   This keeps the title/window left edge aligned with Skills and Contact while
   preventing the square from filling the whole accent rail. */
.github-contributions[data-width="25%"] {
    width: min(100%, 11.5rem);
    margin-right: auto;
}
.github-contributions {
    /* Dark-CV telemetry palette. Tuned from the CV's own page background and
       primary colour, not a fixed navy-blue "terminal" hue — a dark theme
       with zero blue anywhere in its palette (e.g. a warm near-black/brown
       CV) gets a panel that matches its own colours instead of an unrelated
       blue tint. Light CVs get their own tuned variant below
       (.cv-theme-light), which fully overrides every token here. */
    --github-panel: color-mix(in srgb, var(--cv-bg-colour, #070b18) 78%, var(--cv-primary-colour, #4f46e5) 22%);
    --github-panel-raised: color-mix(in srgb, var(--cv-bg-colour, #0d1427) 88%, var(--cv-primary-colour, #4f46e5) 12%);
    --github-ink: var(--github-surface-ink, var(--cv-text-colour, #f1f5f9));
    --github-dim: var(
        --github-surface-dim,
        color-mix(in srgb, var(--cv-text-colour, #f1f5f9) 55%, var(--github-panel))
    );
    --github-rule-colour: var(
        --github-rule,
        color-mix(in srgb, var(--cv-text-colour, #f1f5f9) 18%, transparent)
    );
    --github-accent: color-mix(in srgb, var(--cv-secondary-colour, #22d3ee) 86%, var(--cv-text-colour, #f1f5f9));
    --github-violet: color-mix(in srgb, var(--cv-primary-colour, #818cf8) 82%, var(--cv-text-colour, #f1f5f9));
    --github-cell-base: var(
        --github-empty-cell,
        color-mix(in srgb, var(--cv-primary-colour, #4f46e5) 16%, var(--github-panel))
    );
    --github-cell-stroke: var(
        --github-surface-cell-stroke,
        color-mix(in srgb, var(--cv-text-colour, #f1f5f9) 10%, transparent)
    );
    --github-level-0: var(--github-cell-base);
    --github-level-1: color-mix(in srgb, var(--github-violet) 30%, var(--github-cell-base));
    --github-level-2: color-mix(in srgb, var(--github-violet) 60%, var(--github-cell-base));
    --github-level-3: color-mix(in srgb, var(--github-violet) 64%, var(--github-accent));
    --github-level-4: var(--github-accent);
    --github-edge-colour: color-mix(in srgb, var(--github-accent) 40%, var(--cv-card-border, #34405c));
    --github-default-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.08),
        0 0.8rem 1.8rem rgba(0, 0, 0, 0.28);
    box-sizing: border-box;
    container: github-activity / inline-size;
    width: 100%;
    max-width: 100%;
    min-width: 0;
    overflow: visible;
}
/* Keep the telemetry character in light resumes, but use their own card,
   text, and accent tokens instead of dropping a near-black terminal onto an
   otherwise airy page. Per-section transparent variables remain inherited. */
.cv-theme-light .github-contributions {
    --github-panel: color-mix(
        in srgb,
        var(--cv-card-bg, #ffffff) 88%,
        var(--cv-primary-colour, #4f46e5)
    );
    --github-panel-raised: color-mix(
        in srgb,
        var(--cv-card-bg, #ffffff) 96%,
        var(--cv-primary-colour, #4f46e5)
    );
    --github-ink: var(--github-surface-ink, var(--cv-text-colour, #111827));
    --github-dim: var(
        --github-surface-dim,
        color-mix(in srgb, var(--cv-text-colour, #111827) 58%, var(--github-panel))
    );
    --github-rule-colour: var(
        --github-rule,
        color-mix(in srgb, var(--cv-primary-colour, #4f46e5) 28%, transparent)
    );
    --github-accent: color-mix(
        in srgb,
        var(--cv-secondary-colour, #06b6d4) 58%,
        var(--cv-text-colour, #111827)
    );
    --github-violet: color-mix(
        in srgb,
        var(--cv-primary-colour, #4f46e5) 88%,
        var(--cv-text-colour, #111827)
    );
    --github-cell-base: var(
        --github-empty-cell,
        color-mix(in srgb, var(--cv-primary-colour, #4f46e5) 14%, var(--cv-card-bg, #ffffff))
    );
    --github-cell-stroke: var(
        --github-surface-cell-stroke,
        color-mix(
            in srgb,
            var(--cv-primary-colour, #4f46e5) 24%,
            transparent
        )
    );
    --github-edge-colour: color-mix(
        in srgb,
        var(--cv-primary-colour, #4f46e5) 38%,
        var(--cv-card-border, #e5e7eb)
    );
    --github-default-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.9),
        0 0.65rem 1.5rem color-mix(
            in srgb,
            var(--cv-primary-colour, #4f46e5) 11%,
            transparent
        );
}
.github-contrib-surface {
    position: relative;
    isolation: isolate;
    display: grid;
    grid-template-rows: auto minmax(0, 1fr) auto;
    box-sizing: border-box;
    width: 100%;
    max-width: 100%;
    min-width: 0;
    aspect-ratio: 1;
    min-height: 10.5rem;
    overflow: hidden;
    padding: clamp(0.75rem, 5.5cqi, 1rem);
    border-width: var(--github-edge-width, 1px);
    border-style: solid;
    border-color: var(--github-edge-colour);
    border-radius: var(--github-edge-radius, clamp(1rem, 7cqi, 1.4rem));
    color: var(--github-ink);
    background: var(
        --github-surface-background,
        radial-gradient(circle at 88% 8%, color-mix(in srgb, var(--github-violet) 14%, transparent), transparent 42%),
        linear-gradient(150deg, var(--github-panel-raised), var(--github-panel) 68%)
    );
    background-clip: padding-box;
    box-shadow: var(--github-surface-shadow, var(--github-default-shadow));
    animation: github-contrib-boot 0.72s cubic-bezier(0.16, 1, 0.3, 1) both;
}
.github-contrib-hud,
.github-contrib-months,
.github-contrib-chart,
.github-contrib-foot {
    position: relative;
    z-index: 2;
}
.github-contrib-hud {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 0.65rem;
    min-width: 0;
    padding-bottom: 0.55rem;
    border-bottom: 1px solid var(--github-rule-colour);
    font-family: var(--cv-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
    font-size: clamp(0.48rem, 2.9cqi, 0.62rem);
    font-weight: 700;
    line-height: 1;
    letter-spacing: 0.1em;
    text-transform: uppercase;
}
.github-contrib-node {
    display: none;
    align-items: center;
    flex: 1 1 auto;
    min-width: 0;
    /* No overflow:hidden here: the diamond marker is a rotated square (see
       .github-contrib-node-mark) whose visual bounding box is wider than its
       laid-out 0.42rem box, so this clipped its left tip against the flex
       item's edge. The label span below does its own overflow/ellipsis, so
       this container doesn't need to clip anything itself. */
    color: var(--github-dim);
}
.github-contrib-node > span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.github-contrib-node-label--full {
    display: none;
}
.github-contributions--no-accent-glow {
    --github-accent-glow: none;
}
.github-contributions--no-surface-shadow {
    --github-surface-shadow: none;
}
.github-contrib-node-mark {
    width: 0.42rem;
    height: 0.42rem;
    margin-right: 0.4rem;
    flex: 0 0 auto;
    border: 1px solid var(--github-accent);
    transform: rotate(45deg);
    box-shadow: var(
        --github-accent-glow,
        0 0 0.55rem color-mix(in srgb, var(--github-accent) 55%, transparent)
    );
}
.github-contrib-readout,
.github-contrib-bars {
    display: inline-flex;
    align-items: flex-end;
}
.github-contrib-readout {
    flex: 0 0 auto;
    gap: 0.55rem;
    color: var(--github-accent);
}
.github-contrib-range {
    display: none;
}
.github-contrib-range--compact {
    display: inline;
}
.github-contrib-bars {
    height: 0.65rem;
    gap: 2px;
}
.github-contrib-bars i {
    width: 2px;
    height: 100%;
    border-radius: 99px;
    background: var(--github-accent);
    transform-origin: bottom;
    animation: github-contrib-bars 1.15s ease-in-out infinite alternate;
}
.github-contrib-bars i:nth-child(2) { height: 55%; animation-delay: -0.7s; }
.github-contrib-bars i:nth-child(3) { height: 80%; animation-delay: -0.35s; }
.github-contrib-bars i:nth-child(4) { height: 38%; animation-delay: -0.9s; }
.github-contrib-months {
    display: none;
    gap: clamp(2px, 0.45cqi, 4px);
    margin: 0 0 0.42rem;
    padding-left: 1.65rem;
    overflow: hidden;
    color: var(--github-dim);
    font-family: var(--cv-font-mono, ui-monospace, monospace);
    font-size: 0.5rem;
    line-height: 1;
    letter-spacing: 0.03em;
}
.github-contrib-months > span {
    flex: var(--github-month-weeks, 1) 1 0;
    min-width: 0;
    white-space: nowrap;
}
.github-contrib-chart {
    display: flex;
    align-items: center;
    min-width: 0;
    overflow: hidden;
}
.github-contrib-weekdays {
    display: none;
    grid-template-rows: repeat(7, minmax(0, 1fr));
    align-self: stretch;
    flex: 0 0 1.25rem;
    gap: clamp(2px, 0.45cqi, 4px);
    color: var(--github-dim);
    font-family: var(--cv-font-mono, ui-monospace, monospace);
    font-size: 0.43rem;
    line-height: 1;
}
.github-contrib-weekdays i {
    display: flex;
    align-items: center;
    font-style: normal;
}
.github-contrib-viewport {
    width: 100%;
    min-width: 0;
    overflow: hidden;
    padding-block: 0.35rem;
    -webkit-mask-image: linear-gradient(90deg, transparent, black 8%, black 92%, transparent);
    mask-image: linear-gradient(90deg, transparent, black 8%, black 92%, transparent);
}
.github-contrib-weeks {
    display: flex;
    align-items: flex-start;
    justify-content: flex-start;
    gap: clamp(2px, 1.25cqi, 4px);
    width: max-content;
    min-width: 0;
    padding-inline: 3%;
    animation: github-contrib-pan 7.5s 1s ease-in-out infinite alternate;
}
.github-contrib-week {
    display: grid;
    grid-template-rows: repeat(7, auto);
    flex: 0 0 clamp(0.62rem, 5.2cqi, 0.9rem);
    min-width: 0;
    gap: clamp(2px, 1.25cqi, 4px);
}
.github-contrib-day,
.github-contrib-legend i {
    --github-cell-glow: transparent;
    display: block;
    border-radius: clamp(2px, 1.2cqi, 4px);
    background: var(--github-level-0);
}
.github-contrib-day {
    position: relative;
    width: 100%;
    min-width: 2px;
    aspect-ratio: 1;
    box-shadow:
        inset 0 0 0 1px var(--github-cell-stroke),
        0 0 0.55rem var(--github-cell-glow);
    animation: github-contrib-cell-boot 0.54s cubic-bezier(0.16, 1, 0.3, 1) both;
    animation-delay: calc(80ms + var(--github-week) * 5ms + var(--github-day) * 13ms);
    transition: transform 0.18s ease, filter 0.18s ease, box-shadow 0.18s ease;
}
.github-contrib-day:hover {
    z-index: 3;
    transform: translateY(-2px) scale(1.22);
    filter: saturate(1.35) brightness(1.18);
    box-shadow:
        inset 0 0 0 1px rgba(255, 255, 255, 0.5),
        0 0 0.85rem color-mix(in srgb, var(--github-accent) 58%, transparent);
}
.github-contrib-day[data-level="1"],
.github-contrib-legend i[data-level="1"] { background: var(--github-level-1); }
.github-contrib-day[data-level="2"],
.github-contrib-legend i[data-level="2"] { background: var(--github-level-2); }
.github-contrib-day[data-level="3"],
.github-contrib-legend i[data-level="3"] {
    --github-cell-glow: color-mix(in srgb, var(--github-violet) 24%, transparent);
    background: var(--github-level-3);
}
.github-contrib-day[data-level="4"],
.github-contrib-legend i[data-level="4"] {
    --github-cell-glow: color-mix(in srgb, var(--github-accent) 42%, transparent);
    background: var(--github-level-4);
}
.github-contrib-foot {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 0.65rem;
    min-width: 0;
    padding-top: 0.6rem;
    border-top: 1px solid var(--github-rule-colour);
    animation: github-contrib-foot-boot 0.5s 0.35s ease-out both;
}
.github-contrib-total {
    display: inline-flex;
    align-items: center;
    gap: 0.48rem;
    min-width: 0;
}
.github-contrib-status-dot {
    width: 0.42rem;
    height: 0.42rem;
    flex: 0 0 auto;
    border-radius: 999px;
    background: var(--github-accent);
    box-shadow: none;
    transform-origin: center;
    animation: github-contrib-pulse 2.4s ease-in-out infinite;
}
.github-contributions--no-status-pulse .github-contrib-status-dot {
    animation: none;
    box-shadow: none;
    opacity: 1;
    transform: none;
}
.github-contrib-summary {
    display: none;
    align-items: center;
    min-width: 0;
    color: var(--github-ink);
    line-height: 1;
}
.github-contrib-summary--compact {
    display: inline-flex;
}
.github-contrib-summary strong {
    margin-right: 0.45rem;
    color: var(--github-accent);
    font-family: var(--cv-font-mono, ui-monospace, monospace);
    font-size: clamp(1.15rem, 8.4cqi, 1.55rem);
    font-weight: 850;
    letter-spacing: -0.05em;
    text-shadow: var(
        --github-accent-glow,
        0 0 0.9rem color-mix(in srgb, var(--github-accent) 46%, transparent)
    );
}
.github-contrib-copy {
    display: flex;
    flex-direction: column;
    gap: 0.16rem;
    color: var(--github-ink);
    font-size: clamp(0.48rem, 3.2cqi, 0.65rem);
    font-weight: 700;
    letter-spacing: 0.02em;
}
.github-contrib-copy small {
    color: var(--github-dim);
    font-family: var(--cv-font-mono, ui-monospace, monospace);
    font-size: 0.72em;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}
.github-contrib-legend {
    display: none;
    align-items: center;
    flex: 0 0 auto;
    gap: 0.22rem;
    color: var(--github-dim);
    font-family: var(--cv-font-mono, ui-monospace, monospace);
    font-size: 0.42rem;
    letter-spacing: 0.08em;
}
.github-contrib-legend i {
    width: 0.46rem;
    height: 0.46rem;
    border-radius: 2px;
    box-shadow: inset 0 0 0 1px var(--github-cell-stroke);
}
/* Compact cards: latest 16 weeks, gently panning inside a square viewport. */
.github-contrib-week:nth-last-child(n + 17) {
    display: none;
}
/* Medium cards: horizontal 36-week telemetry panel. */
@container github-activity (min-width: 330px) {
    .github-contrib-surface {
        aspect-ratio: auto;
        min-height: 10.5rem;
        padding: clamp(0.8rem, 2.8cqi, 1.15rem);
    }
    .github-contrib-hud {
        justify-content: space-between;
    }
    .github-contrib-node {
        display: inline-flex;
    }
    .github-contrib-node-label--full {
        display: inline;
    }
    .github-contrib-range--compact,
    .github-contrib-summary--compact {
        display: none;
    }
    .github-contrib-range--medium {
        display: inline;
    }
    .github-contrib-summary--medium {
        display: inline-flex;
    }
    .github-contrib-viewport {
        -webkit-mask-image: none;
        mask-image: none;
    }
    .github-contrib-weeks {
        width: 100%;
        padding-inline: 0;
        gap: clamp(2px, 0.45cqi, 4px);
        animation: none;
    }
    .github-contrib-week {
        flex: 1 1 0;
        gap: clamp(2px, 0.45cqi, 4px);
    }
    .github-contrib-week:nth-last-child(-n + 36) {
        display: grid;
    }
    .github-contrib-summary strong {
        font-size: 1.15rem;
    }
    .github-contrib-copy {
        font-size: 0.61rem;
    }
    .github-contrib-legend {
        display: inline-flex;
    }
}
/* Wide cards: labelled full-year telemetry panel. */
@container github-activity (min-width: 560px) {
    .github-contrib-surface {
        grid-template-rows: auto auto minmax(0, 1fr) auto;
        min-height: 12rem;
    }
    .github-contrib-range--medium,
    .github-contrib-summary--medium {
        display: none;
    }
    .github-contrib-range--wide {
        display: inline;
    }
    .github-contrib-summary--wide {
        display: inline-flex;
    }
    .github-contrib-months {
        display: flex;
        margin-top: 0.7rem;
    }
    .github-contrib-weekdays {
        display: grid;
    }
    .github-contributions .github-contrib-week {
        display: grid;
    }
}
.github-contrib-empty {
    box-sizing: border-box;
    max-width: 100%;
    margin: 0;
    overflow: hidden;
    color: var(--cv-muted-colour, #6b7280);
    font-size: 0.7rem;
    line-height: 1.4;
}
@keyframes github-contrib-boot {
    from { opacity: 0; transform: translateY(0.65rem) scale(0.965); }
    to { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes github-contrib-bars {
    from { transform: scaleY(0.35); opacity: 0.45; }
    to { transform: scaleY(1); opacity: 1; }
}
@keyframes github-contrib-pan {
    from { transform: translateX(0); }
    to { transform: translateX(calc(100cqi - 100% - clamp(1.5rem, 11cqi, 2rem))); }
}
@keyframes github-contrib-cell-boot {
    from { opacity: 0; transform: translateY(0.32rem) scale(0.45); }
    to { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes github-contrib-foot-boot {
    from { opacity: 0; transform: translateY(0.3rem); }
    to { opacity: 1; transform: translateY(0); }
}
@keyframes github-contrib-pulse {
    0%, 100% {
        opacity: 0.72;
        transform: scale(0.88);
        box-shadow: 0 0 0.2rem color-mix(in srgb, var(--github-accent) 16%, transparent);
    }
    50% {
        opacity: 1;
        transform: scale(1);
        box-shadow: 0 0 0.48rem color-mix(in srgb, var(--github-accent) 34%, transparent);
    }
}
.github-refresh-button {
    min-height: 2.75rem;
    padding-block: 0;
    text-align: center;
}
.github-refresh-button > span {
    width: 100%;
    align-items: center;
    justify-content: center;
    line-height: 1;
}
/* HTMX hides indicators with opacity only, which still reserves width and
   pushes the idle label off-centre. Keep exactly one state in layout. */
.github-refresh-loading {
    display: none !important;
}
.github-refresh-loading.htmx-request,
.htmx-request .github-refresh-loading {
    display: inline-flex !important;
}
.github-refresh-idle {
    display: inline-flex;
}
.github-refresh-loading.htmx-request + .github-refresh-idle,
.htmx-request .github-refresh-idle {
    display: none;
}
@media print, (prefers-reduced-motion: reduce) {
    .github-contrib-surface,
    .github-contrib-bars i,
    .github-contrib-weeks,
    .github-contrib-day,
    .github-contrib-foot,
    .github-contrib-status-dot {
        animation: none;
        transition: none;
    }
}
