/* The Linktree: one full-height, themed section per family member.

   Colours live in three custom properties -- --lt-from and --lt-to for the
   gradient, --lt-ink for text on it. The pill inverts that pair: --lt-ink as
   its surface, --lt-to as its text. One contrast check therefore covers every
   text-on-background pairing on the page, which is what apps/profiles/forms.py
   validates before anything is published.

   The theme classes below are generated from apps/profiles/themes.py;
   tests/profiles/test_themes.py fails if the two drift apart. Optional custom
   colours are applied on top at runtime by static/js/linktree.js through
   element.style.setProperty(), because CSSOM writes are not gated by the
   style-src CSP directive while parsed style="..." markup is. With JavaScript
   off, the theme class alone still paints every section correctly. */

:root {
    --lt-from: #c2410c;
    --lt-to: #7f1d1d;
    --lt-ink: #ffffff;
}

.lt-theme--ember {
    --lt-from: #c2410c;
    --lt-to: #7f1d1d;
    --lt-ink: #ffffff;
}

.lt-theme--sunset {
    --lt-from: #b45309;
    --lt-to: #9f1239;
    --lt-ink: #ffffff;
}

.lt-theme--lava {
    --lt-from: #a16207;
    --lt-to: #7c2d12;
    --lt-ink: #ffffff;
}

.lt-theme--berry {
    --lt-from: #be185d;
    --lt-to: #701a75;
    --lt-ink: #ffffff;
}

.lt-theme--orchid {
    --lt-from: #7e22ce;
    --lt-to: #4a044e;
    --lt-ink: #ffffff;
}

.lt-theme--dusk {
    --lt-from: #4f46e5;
    --lt-to: #312e81;
    --lt-ink: #ffffff;
}

.lt-theme--sky {
    --lt-from: #0369a1;
    --lt-to: #1e3a8a;
    --lt-ink: #ffffff;
}

.lt-theme--tide {
    --lt-from: #0e7490;
    --lt-to: #134e4a;
    --lt-ink: #ffffff;
}

.lt-theme--mint {
    --lt-from: #047857;
    --lt-to: #064e3b;
    --lt-ink: #ffffff;
}

.lt-theme--forest {
    --lt-from: #15803d;
    --lt-to: #14532d;
    --lt-ink: #ffffff;
}

.lt-theme--sand {
    --lt-from: #92400e;
    --lt-to: #451a03;
    --lt-ink: #ffffff;
}

.lt-theme--slate {
    --lt-from: #475569;
    --lt-to: #0f172a;
    --lt-ink: #ffffff;
}

/* --- The page ------------------------------------------------------------
   A fixed backdrop sits behind everything and carries the *active* member's
   colours, so the overscroll area, the gaps and the browser chrome follow the
   section you are reading. linktree.js mirrors the active section's three
   properties onto :root; with scripting off it keeps the defaults above, and
   every section still paints its own gradient. */
.lt-page {
    position: relative;
    min-height: 100vh;
}

.lt-backdrop {
    position: fixed;
    inset: 0;
    z-index: -1;
    background: linear-gradient(180deg, var(--lt-from), var(--lt-to));
    transition: background 600ms ease;
}

@media (prefers-reduced-motion: reduce) {
    .lt-backdrop {
        transition: none;
    }
}

.lt-section {
    display: flex;
    min-height: 100svh;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--app-space-4);
    padding: var(--app-space-8) var(--app-space-4);
    color: var(--lt-ink);
    background: linear-gradient(180deg, var(--lt-from), var(--lt-to));
    text-align: center;
}

.lt-card {
    width: min(100%, 26rem);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--app-space-3);
}

/* --- Identity ------------------------------------------------------------ */
.lt-avatar {
    width: 8rem;
    height: 8rem;
    border-radius: var(--app-radius-pill);
    border: 4px solid var(--lt-ink);
    object-fit: cover;
    background: var(--lt-ink);
}

.lt-avatar--placeholder {
    display: grid;
    place-items: center;
    color: var(--lt-to);
    font-size: 2.75rem;
    font-weight: 800;
    line-height: 1;
}

.lt-name {
    color: var(--lt-ink);
    font-size: var(--app-text-xl);
    font-weight: 800;
    letter-spacing: -0.02em;
}

.lt-handle {
    opacity: 0.85;
    font-size: var(--app-text-sm);
    font-variant-numeric: tabular-nums;
}

.lt-tagline {
    opacity: 0.92;
    font-size: var(--app-text-sm);
    text-wrap: balance;
}

/* --- Pill buttons --------------------------------------------------------
   The pill inverts the section pair: the ink colour becomes its surface and
   the gradient's dark end becomes its text. */
.lt-links {
    width: 100%;
    display: flex;
    flex-direction: column;
    gap: var(--app-space-3);
    margin-block-start: var(--app-space-3);
}

.lt-link {
    /* Relative, so the icon can be taken out of the flow: centring the label in
       the remaining space would shift it whenever an icon is present, and the
       column of pills would no longer line up. */
    position: relative;
    display: block;
    padding: 0.85rem 3rem;
    text-align: center;
    border-radius: var(--app-radius-pill);
    color: var(--lt-to);
    background: var(--lt-ink);
    font-weight: 700;
    text-decoration: none;
    box-shadow: 0 8px 24px rgb(0 0 0 / 18%);
    transition: transform 160ms ease, box-shadow 160ms ease;
}

.lt-link:hover,
.lt-link:focus-visible {
    transform: translateY(-2px);
    color: var(--lt-to);
    box-shadow: 0 12px 32px rgb(0 0 0 / 26%);
}

.lt-link__icon {
    position: absolute;
    inset-block: 0;
    inset-inline-start: 1rem;
    display: flex;
    align-items: center;
}

.lt-link__icon svg {
    width: 1.15rem;
    height: 1.15rem;
    fill: currentColor;
    opacity: 0.75;
}

/* Wrapping is the one case the absolute icon cannot follow, so a long label
   keeps its distance from it rather than running underneath. */
.lt-link__label {
    display: block;
}

/* --- Social row ---------------------------------------------------------- */
.lt-follow {
    margin-block-start: var(--app-space-5);
    opacity: 0.9;
    font-size: var(--app-text-xs);
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
}

.lt-socials {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--app-space-3);
    margin-block-start: var(--app-space-2);
}

.lt-social {
    display: grid;
    width: 2.75rem;
    height: 2.75rem;
    place-items: center;
    border-radius: var(--app-radius-pill);
    color: var(--lt-ink);
    background: color-mix(in srgb, var(--lt-to) 78%, #000);
    transition: transform 160ms ease;
}

.lt-social:hover,
.lt-social:focus-visible {
    transform: translateY(-2px);
    color: var(--lt-ink);
}

.lt-social svg {
    width: 1.25rem;
    height: 1.25rem;
    fill: currentColor;
}

/* --- Section switching ---------------------------------------------------
   The index reads as a stack of screens rather than one long page: the
   viewport rests on one member at a time, and a scroll gesture changes which
   one. That is CSS scroll snapping on the document itself -- no scroll
   handler, so the browser keeps its own inertia, keyboard and trackpad
   behaviour, and with this stylesheet blocked the page is an ordinary scroll.

   The snap container is the document element, not body, so the rule has to
   reach it from the class on .lt-page with :has(). Only the index carries that
   class: the detail page is a single section with nothing to switch between.

   A member with many links can be taller than the viewport. That still scrolls
   freely -- when the snap area is larger than the snapport the container may
   rest anywhere that covers it -- but on a screen too short for even a plain
   section, mandatory would fight the reader, so below that height snapping
   drops to proximity. */
:root:has(.lt-page--snap) {
    scroll-behavior: smooth;
    scroll-snap-type: y proximity;
}

@media (min-height: 34rem) {
    :root:has(.lt-page--snap) {
        scroll-snap-type: y mandatory;
    }
}

/* Only the smooth part is animation; snapping decides where a scroll ends, not
   how it travels. */
@media (prefers-reduced-motion: reduce) {
    :root:has(.lt-page--snap) {
        scroll-behavior: auto;
    }
}

.lt-page--snap .lt-section {
    scroll-snap-align: start;
}

/* The footer sits below the last section's snap point, so without a snap
   position of its own mandatory snapping pulls the reader straight back off
   it. */
.lt-page--snap .site-footer {
    scroll-snap-align: end;
}

/* --- Overview panel ------------------------------------------------------
   The first screen of the index, shown only when there is more than one member
   -- a table of contents for a single entry is noise. Handles are both the
   sort key and the label, because /@christian is what the address bar shows
   and what gets passed on by hand; the display name rides underneath. */
.lt-overview__list {
    width: 100%;
    display: flex;
    flex-direction: column;
    gap: var(--app-space-3);
    margin-block-start: var(--app-space-4);
}

/* Layout only: the pill's colours, radius and shadow come from .lt-link. */
.lt-overview__item {
    display: flex;
    align-items: center;
    gap: var(--app-space-3);
    padding: 0.6rem 1.1rem;
    text-align: start;
}

.lt-overview__avatar {
    width: 2.5rem;
    height: 2.5rem;
    flex: none;
    border-radius: var(--app-radius-pill);
    color: var(--lt-ink);
    object-fit: cover;
    background: var(--lt-to);
}

.lt-overview__avatar--placeholder {
    display: grid;
    place-items: center;
    font-size: var(--app-text-sm);
    font-weight: 800;
    line-height: 1;
}

.lt-overview__text {
    min-width: 0;
    display: flex;
    flex-direction: column;
}

.lt-overview__handle {
    overflow-wrap: anywhere;
}

.lt-overview__name {
    font-size: var(--app-text-xs);
    font-weight: 600;
    opacity: 0.7;
}

/* --- Jump rail -----------------------------------------------------------
   Without it the index is a page you can only scroll through. Hidden when
   there is a single member, and hidden on narrow screens where it would sit
   on top of the content. */
.lt-rail {
    position: fixed;
    top: 50%;
    right: var(--app-space-4);
    z-index: 20;
    display: flex;
    flex-direction: column;
    gap: var(--app-space-2);
    translate: 0 -50%;
}

.lt-rail__dot {
    width: 0.7rem;
    height: 0.7rem;
    border: 2px solid var(--lt-ink);
    border-radius: var(--app-radius-pill);
    opacity: 0.45;
    background: transparent;
    transition: opacity 160ms ease, background 160ms ease;
}

.lt-rail__dot[aria-current="true"] {
    opacity: 1;
    background: var(--lt-ink);
}

@media (max-width: 767.98px) {
    .lt-rail {
        display: none;
    }
}

/* --- Footer -------------------------------------------------------------- */
/* The footer itself is styled in layout.css and shared with the chrome pages.
   Only its ink differs here, because on the Linktree it sits on the member's
   gradient rather than on the dark chrome ground. */
.lt-page {
    --footer-ink: var(--lt-ink);
}
