/* THIS FILE MUST NOT CONTAIN BRAND COLORS.
 *
 * The brand ramp (--gp-brand, -dark, -deep, -soft, -pale) and the DaisyUI theme
 * tokens (--p/--s/--a/--in and their content colors) are DERIVED at startup from
 * a single hex -- GP_BRAND_COLOR -- by app/services/branding.py, and injected as
 * a <style> block in base.html. Opening the app to another colla means setting
 * that one environment variable and nothing else.
 *
 * So: reach for var(--gp-brand-*) here, never a literal. For a tint or a shadow,
 * use color-mix(in srgb, var(--gp-brand) 30%, transparent) rather than an rgba()
 * with the blue baked in -- an rgba() literal will not follow the brand.
 *
 * Status colors are the deliberate exception: success/warning/error (and the
 * red/green quick-action pills) must stay semantically distinct from whatever
 * color a colla picks, so DaisyUI's stock --su/--wa/--er are left untouched.
 *
 * The fixed-lightness ladder in branding.py is what keeps this safe: every step
 * has a pinned OKLCH lightness, so white stays AA-legible on the dark steps and
 * black on the light ones no matter which hue is supplied.
 */
:root {
    --gp-topbar-bg: rgba(255, 255, 255, 0.66);
    --gp-topbar-border: rgba(15, 23, 42, 0.08);
    --gp-pill-text: #0f172a;
    --gp-pill-active: #0f172a;
    --gp-pill-active-text: #f8fafc;
    --gp-font-body: "Space Grotesk", "Segoe UI", sans-serif;
    --gp-font-display: "Fraunces", Georgia, serif;
    --gp-panel-bg: rgba(255, 255, 255, 0.74);
    --gp-panel-border: rgba(15, 23, 42, 0.1);
    --gp-shadow-soft: 0 14px 32px rgba(15, 23, 42, 0.08);
}

html,
body {
    min-height: 100vh;
}

body {
    font-family: var(--gp-font-body);
    color: #102035;
    background:
    radial-gradient(circle at 8% -14%, color-mix(in srgb, var(--gp-brand) 30%, transparent), transparent 40%),
    radial-gradient(circle at 92% -10%, color-mix(in srgb, var(--gp-brand-soft) 28%, transparent), transparent 38%),
    linear-gradient(180deg, color-mix(in srgb, var(--gp-brand-pale) 30%, #ffffff) 0%, var(--gp-brand-pale) 58%, color-mix(in srgb, var(--gp-brand-pale) 72%, #ffffff) 100%);
}

h1,
h2,
h3,
.card-title {
    font-family: var(--gp-font-display);
    letter-spacing: 0.01em;
}

.site-title {
    font-family: var(--gp-font-display);
    font-weight: 800;
    letter-spacing: 0.01em;
}

.site-brand {
    display: inline-grid;
    grid-auto-flow: column;
    grid-auto-columns: max-content;
    align-items: center;
    gap: 0.45rem;
    white-space: nowrap;
    text-decoration: none;
    color: inherit;
}

.site-brand-logo {
    display: block;
    width: 32px;
    min-width: 32px;
    max-width: 32px;
    height: 54px;
    min-height: 54px;
    max-height: 54px;
    flex: 0 0 54px;
    object-fit: cover;
    border-radius: 0.3rem;
    border: 1px solid rgba(15, 23, 42, 0.2);
    background: rgba(255, 255, 255, 0.9);
    box-shadow: 0 4px 10px rgba(15, 23, 42, 0.12);
    transform: rotate(0deg);
    transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.site-brand:hover .site-brand-logo,
.site-brand:focus-visible .site-brand-logo {
    transform: rotate(0deg) translateY(0px) scale(1.04);
    box-shadow: 0 12px 22px rgba(15, 23, 42, 0.16);
}

.site-brand .site-title {
    display: inline-block;
    white-space: nowrap;
}

.site-header-layout {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.site-header-brand {
    min-width: 0;
}

.site-header-nav {
    display: flex;
    justify-content: center;
    min-width: 0;
}

.site-header-actions {
    align-self: flex-end;
}

.site-topbar {
    position: sticky;
    top: 0;
    z-index: 60;
    border-bottom: 1px solid var(--gp-topbar-border);
    background: var(--gp-topbar-bg);
    backdrop-filter: blur(12px) saturate(1.4);
}

.gp-page-loader {
    position: fixed;
    top: 0;
    left: 0;
    width: 0;
    height: 3px;
    z-index: 200;
    opacity: 0;
    pointer-events: none;
    background: linear-gradient(90deg, var(--gp-brand), var(--gp-brand-soft));
    transition: width 0.2s ease, opacity 0.2s ease;
}

.gp-page-loader.is-loading {
    opacity: 1;
    transition: width 0.3s cubic-bezier(0.1, 0.6, 0.2, 1), opacity 0.15s ease;
}

.gp-page-loader.is-loading.is-growing {
    width: 88%;
    transition: width 6s cubic-bezier(0.1, 0.6, 0.2, 1), opacity 0.15s ease;
}

.gp-page-loader.is-done {
    width: 100%;
    opacity: 0;
    transition: width 0.2s ease, opacity 0.3s ease 0.15s;
}

/* min-width: 0 lets the shell shrink to its column so .apple-nav scrolls
   instead of spilling over the brand and actions. */
.apple-nav-shell {
    min-width: 0;
    max-width: 100%;
    border: 1px solid rgba(15, 23, 42, 0.08);
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.68);
    padding: 0.25rem;
    box-shadow: 0 12px 24px rgba(15, 23, 42, 0.06);
}

.apple-nav {
    display: flex;
    flex-wrap: nowrap;
    justify-content: safe center;
    gap: 0.4rem;
    overflow-x: auto;
    scrollbar-width: none;
}

.apple-nav::-webkit-scrollbar {
    display: none;
}

.apple-nav .nav-pill {
    flex-shrink: 0;
    white-space: nowrap;
}

.mobile-nav-shell {
    width: 100%;
}

.mobile-nav-shell::-webkit-scrollbar {
    display: none;
}

.mobile-nav {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(4.5rem, 1fr));
    gap: 0.35rem;
    width: 100%;
    border: 1px solid rgba(15, 23, 42, 0.08);
    border-radius: 1.25rem;
    background: rgba(255, 255, 255, 0.7);
    padding: 0.25rem;
    box-shadow: 0 10px 20px rgba(15, 23, 42, 0.06);
}

.mobile-nav-pill {
    display: flex;
    align-items: center;
    justify-content: center;
    min-width: 0;
    min-height: 2.5rem;
    height: auto;
    white-space: normal;
    text-align: center;
    line-height: 1.15;
    overflow-wrap: anywhere;
    word-break: break-word;
    hyphens: auto;
}

.nav-pill {
    border: none;
    border-radius: 999px;
    padding: 0.45rem 0.9rem;
    font-size: 0.86rem;
    font-weight: 600;
    color: var(--gp-pill-text);
    transition: background-color 0.16s ease, color 0.16s ease;
}

.nav-pill:hover {
    background: rgba(15, 23, 42, 0.12);
}

.nav-pill.nav-pill-active {
    background: var(--gp-pill-active);
    color: var(--gp-pill-active-text);
}

#main-content {
    animation: gp-fade-up 0.38s ease;
}

#main-content.gp-content-enter {
    animation: gp-fade-up 0.38s ease;
}

#main-content.gp-content-exit {
    opacity: 0.55;
    transform: translateY(4px);
    transition: opacity 0.18s ease, transform 0.18s ease;
}

.card,
.stats,
.alert,
.modal-box,
.table,
#calendar-view > .overflow-x-auto,
#calendar-view > .flex {
    border: 1px solid var(--gp-panel-border);
    background: var(--gp-panel-bg);
    box-shadow: var(--gp-shadow-soft);
    backdrop-filter: blur(6px);
}

.modal-box {
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
}

.btn {
    border-radius: 999px;
    font-weight: 700;
    letter-spacing: 0.01em;
}

.badge {
    font-weight: 600;
    border-radius: 999px;
}

.input,
.select,
.textarea {
    border-radius: 0.9rem;
}

.table thead tr {
    font-family: var(--gp-font-body);
    letter-spacing: 0.02em;
}

.admin-tabs {
    --tab-border-color: rgba(148, 163, 184, 0.42);
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

.admin-tabs > .tab {
    white-space: nowrap;
    border-top-left-radius: 1rem;
    border-top-right-radius: 1rem;
    color: #102035;
    background: #f1f5f9;
}

.admin-tabs > .tab:is(:checked, .tab-active) {
    background: #ffffff;
    color: #0f172a;
}

.admin-tab-panel {
    display: none;
    border-color: var(--gp-panel-border);
    background: #ffffff;
    margin-top: 0;
    border-top-left-radius: 0;
}

.admin-tab-panel.is-active {
    display: block;
}

.diades-shell {
    --diades-ink: #162238;
}

.diades-hero {
    position: relative;
    overflow: hidden;
    border-radius: 2rem;
    border: 1px solid rgba(15, 23, 42, 0.12);
    background:
        radial-gradient(circle at 0% 0%, color-mix(in srgb, var(--gp-brand) 22%, transparent), transparent 42%),
        radial-gradient(circle at 100% 0%, color-mix(in srgb, var(--gp-brand-soft) 24%, transparent), transparent 38%),
        linear-gradient(145deg,
            color-mix(in srgb, var(--gp-brand-pale) 26%, #ffffff) 0%,
            #f8fafc 45%,
            color-mix(in srgb, var(--gp-brand-pale) 18%, #ffffff) 100%);
    box-shadow: 0 24px 58px rgba(15, 23, 42, 0.1);
}

.diades-hero-orb {
    position: absolute;
    border-radius: 999px;
    filter: blur(6px);
    pointer-events: none;
}

.diades-hero-orb-a {
    width: 170px;
    height: 170px;
    left: -50px;
    bottom: -70px;
    background: color-mix(in srgb, var(--gp-brand) 20%, transparent);
}

.diades-hero-orb-b {
    width: 210px;
    height: 210px;
    right: -60px;
    top: -80px;
    background: color-mix(in srgb, var(--gp-brand-soft) 22%, transparent);
}

.diades-kicker {
    display: inline-flex;
    align-items: center;
    border-radius: 999px;
    border: 1px solid rgba(15, 23, 42, 0.12);
    background: rgba(255, 255, 255, 0.8);
    padding: 0.34rem 0.72rem;
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--diades-ink);
}

.diada-card {
    border-radius: 1.4rem;
    border: 2px solid rgba(15, 23, 42, 0.16);
    background: #ffffff;
    box-shadow: 0 18px 38px rgba(15, 23, 42, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.65);
    padding: 1.15rem;
    transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.diada-card-button {
    width: 100%;
    text-align: left;
    cursor: pointer;
}

.diada-card-button:focus-visible {
    outline: 3px solid color-mix(in srgb, var(--gp-brand) 40%, transparent);
    outline-offset: 3px;
}

.diada-card:hover {
    transform: translateY(-4px);
    box-shadow: 0 24px 42px rgba(15, 23, 42, 0.14);
}

.diada-card-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.6rem;
}

.diada-type-pill {
    display: inline-flex;
    align-items: center;
    border-radius: 999px;
    background: var(--gp-brand-dark);
    border: 1px solid var(--gp-brand-dark);
    color: #ffffff;
    padding: 0.18rem 0.62rem;
    font-size: 0.73rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.diada-date {
    font-size: 0.82rem;
    font-weight: 700;
    color: #334155;
}

.diada-empty-state {
    border-radius: 1.4rem;
    border: 1px dashed rgba(15, 23, 42, 0.2);
    background: rgba(255, 255, 255, 0.75);
    padding: 1.5rem;
}

.gp-calendar-toolbar {
    background:
        linear-gradient(145deg, rgba(255, 255, 255, 0.88), rgba(241, 245, 249, 0.82)),
        var(--gp-panel-bg);
}

.gp-calendar-grid {
    align-items: stretch;
}

.gp-calendar-weekday {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.gp-calendar-day {
    transition: box-shadow 0.2s ease, transform 0.2s ease;
}

.gp-calendar-day:hover {
    transform: translateY(-1px);
    box-shadow: 0 8px 20px rgba(15, 23, 42, 0.08);
}

.gp-calendar-day-today {
    border-color: rgba(14, 116, 144, 0.45);
    box-shadow: inset 0 0 0 1px rgba(14, 116, 144, 0.2);
    background: linear-gradient(180deg, rgba(224, 242, 254, 0.65), rgba(255, 255, 255, 0.95));
}

.gp-calendar-day-past {
    background: linear-gradient(180deg, rgba(248, 250, 252, 0.85), rgba(241, 245, 249, 0.8));
}

.gp-calendar-event {
    backdrop-filter: blur(3px);
}

.gp-timetable-table tbody tr {
    transition: background-color 0.18s ease;
}

.gp-timetable-table tbody tr:hover {
    background-color: rgba(15, 23, 42, 0.04);
}

.gp-past-events-details > summary {
    user-select: none;
}

.gp-past-events-details > summary::-webkit-details-marker {
    display: none;
}

.gp-past-events-details > summary::after {
    content: "+";
    font-weight: 700;
    opacity: 0.65;
}

.gp-past-events-details[open] > summary::after {
    content: "-";
}

.gp-designs-collapsible > summary {
    list-style: none;
}

.gp-designs-collapsible > summary::-webkit-details-marker {
    display: none;
}

.gp-designs-collapsible > summary .gp-designs-collapsible-caret {
    display: inline-block;
    transition: transform 0.15s ease;
}

.gp-designs-collapsible[open] > summary .gp-designs-collapsible-caret {
    transform: rotate(180deg);
}

.my-assignment-shell {
    max-width: 1100px;
    margin: 0 auto;
}

.my-assignment-design-list {
    display: flex;
    flex-direction: column;
    gap: 0.55rem;
}

.my-assignment-design-card {
    border: 1px solid rgba(148, 163, 184, 0.45);
    border-radius: 0.95rem;
    background: linear-gradient(180deg, rgba(255, 255, 255, 0.9), rgba(248, 250, 252, 0.88));
    padding: 0.7rem;
}

.my-assignment-pos-tag {
    display: inline-flex;
    align-items: center;
    border: 1px solid var(--gp-brand-soft);
    background: color-mix(in srgb, var(--gp-brand-pale) 45%, #ffffff);
    color: var(--gp-brand-deep);
    border-radius: 999px;
    padding: 0.16rem 0.62rem;
    font-size: 0.72rem;
    font-weight: 700;
    line-height: 1.2;
    letter-spacing: 0.01em;
}

.my-assignment-schema-panel {
    border: 1px solid rgba(148, 163, 184, 0.45);
    border-radius: 1rem;
    background: linear-gradient(180deg, rgba(255, 255, 255, 0.92), rgba(241, 245, 249, 0.84));
    padding: 0.5rem;
    overflow: hidden;
}

.my-assignment-legend-panel {
    border: 1px solid rgba(148, 163, 184, 0.45);
    border-radius: 1rem;
    background: linear-gradient(180deg, rgba(255, 255, 255, 0.94), rgba(248, 250, 252, 0.88));
    padding: 0.7rem;
}

.my-assignment-legend-item {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    border-radius: 0.6rem;
    border: 1px solid rgba(148, 163, 184, 0.42);
    background: #ffffff;
    padding: 0.38rem 0.5rem;
}

.legend-dot {
    display: inline-block;
    width: 14px;
    height: 14px;
    border-radius: 999px;
    border: 1px solid #334155;
    flex-shrink: 0;
}

.assignment-schema-wrap {
    width: 100%;
    max-width: 100%;
}

.assignment-schema :where(svg) {
    display: block;
    width: 100%;
    height: auto;
    max-width: 100%;
    max-height: min(80vh, 940px);
}

@keyframes gp-fade-up {
    from {
        opacity: 0;
        transform: translateY(6px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

.gp-event-modal {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity 0.28s ease, visibility 0s linear 0.28s;
}

.gp-event-modal.is-open {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transition: opacity 0.32s ease, visibility 0s linear 0s;
}

.gp-event-modal-overlay {
    opacity: 0;
    transition: opacity 0.3s ease;
}

.gp-event-modal.is-open .gp-event-modal-overlay {
    opacity: 1;
}

.gp-event-modal-panel {
    opacity: 0.9;
    transform: translateX(2.25rem) scale(0.992);
    transition: transform 0.38s cubic-bezier(0.2, 0.9, 0.2, 1), opacity 0.32s ease;
    will-change: transform, opacity;
}

.gp-event-modal.is-open .gp-event-modal-panel {
    opacity: 1;
    transform: translateX(0) scale(1);
}

#event-detail-panel {
    transform-origin: top right;
    transition: opacity 0.2s ease, transform 0.2s ease;
}

#event-detail-panel.is-loading {
    opacity: 0.55;
    transform: translateY(0.3rem);
}

#event-detail-panel.is-loaded {
    animation: gp-panel-in 0.24s ease;
}

@keyframes gp-panel-in {
    from {
        opacity: 0.62;
        transform: translateY(0.35rem);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

@media (prefers-reduced-motion: reduce) {
    .gp-event-modal,
    .gp-event-modal-overlay,
    .gp-event-modal-panel,
    #event-detail-panel,
    #main-content,
    #main-content.gp-content-enter,
    #main-content.gp-content-exit {
        animation: none !important;
        transition-duration: 0.01ms !important;
    }
}

@media (max-width: 768px) {
    .site-title {
        font-size: 1.55rem;
    }

    .site-header-actions {
        width: 100%;
    }

    .site-brand {
        gap: 0.38rem;
        max-width: 100%;
    }

    .site-brand-logo {
        width: 14px;
        min-width: 14px;
        max-width: 14px;
        height: 22px;
        min-height: 22px;
        max-height: 22px;
        flex-basis: 22px;
    }

    .site-brand .site-title {
        overflow: hidden;
        text-overflow: ellipsis;
    }

    .apple-nav {
        gap: 0.3rem;
    }

    .nav-pill {
        font-size: 0.8rem;
        padding: 0.4rem 0.7rem;
    }

    .mobile-nav {
        gap: 0.3rem;
        grid-template-columns: repeat(auto-fit, minmax(4.1rem, 1fr));
    }

    .mobile-nav-pill {
        font-size: 0.68rem;
        padding: 0.38rem 0.32rem;
        min-height: 2.4rem;
        line-height: 1.1;
    }

    .gp-calendar-board {
        padding-bottom: 0.65rem;
    }

    .gp-calendar-weekdays,
    .gp-calendar-grid {
        min-width: 640px;
    }

    .gp-calendar-weekdays {
        font-size: 0.65rem;
        letter-spacing: 0.02em;
    }

    .gp-calendar-day {
        min-height: 5.5rem;
    }

    .assignment-schema-container {
        overflow: hidden;
    }

    .assignment-schema-wrap {
        width: 100%;
    }

    .assignment-schema :where(svg) {
        width: 100%;
        max-width: 100%;
        height: auto;
    }
}

/* Desktop header: brand + actions share the top row and the nav gets its own
   centred row; the three only sit side by side from 1280px, the widest they
   need (French labels) once the avatar is in the actions. */
@media (min-width: 768px) {
    .site-header-layout {
        display: grid;
        grid-template-columns: minmax(0, 1fr) auto;
        align-items: center;
        column-gap: 1rem;
        row-gap: 0.6rem;
    }

    .site-header-brand {
        grid-column: 1;
        grid-row: 1;
    }

    .site-brand {
        max-width: 100%;
        grid-template-columns: max-content minmax(0, max-content);
    }

    .site-brand .site-title {
        overflow: hidden;
        text-overflow: ellipsis;
    }

    .site-header-nav {
        grid-column: 1 / -1;
        grid-row: 2;
    }

    .site-header-actions {
        grid-column: 2;
        grid-row: 1;
        width: auto;
        align-self: center;
    }

    /* Avatar only: the name is already in the button's tooltip and aria-label. */
    .site-header-actions .gp-topbar-avatar {
        padding: 0.2rem;
    }

    .site-header-actions .gp-topbar-avatar-name {
        display: none;
    }
}

@media (min-width: 1280px) {
    .site-header-layout {
        grid-template-columns: auto minmax(0, 1fr) auto;
        row-gap: 0;
    }

    .site-header-nav {
        grid-column: 2;
        grid-row: 1;
    }

    .site-header-actions {
        grid-column: 3;
    }
}

/* Mobile-only floating chrome: replaces the top header entirely on phones.
   .gp-has-floating-nav is set on <body> so #main-content reserves clearance
   for the fixed bottom island regardless of which main_class block a page
   uses. */
body.gp-has-floating-nav {
    /* Total space the bottom island occupies, measured from the viewport
       bottom: safe area + the island's offset + its height + a breathing gap.
       Pages with their own bottom-anchored controls (notably the designer
       canvas) read this to lift those controls clear of the island, so it
       must stay in sync with .gp-floating-bottom-nav's bottom offset and the
       height of .gp-floating-nav-item plus its wrapper padding. */
    --gp-floating-nav-clearance: calc(env(safe-area-inset-bottom, 0px) + 6.25rem);
}

body.gp-has-floating-nav #main-content {
    padding-bottom: var(--gp-floating-nav-clearance);
}

/* The event slide-over is a fixed, self-scrolling box, so #main-content's
   clearance never reaches it: its last controls (the friend/family sign-up
   field and its submit button) ended up under the island with no scroll left
   to free them. Give the panel's own scroll box the same clearance. Any other
   panel that scrolls independently of #main-content needs this too. */
body.gp-has-floating-nav .gp-event-modal-panel {
    padding-bottom: var(--gp-floating-nav-clearance);
}

/* The swap target must not be transformed while the floating island is on
   screen. A transform on #main-content makes the fixed island resolve its
   `bottom` against the document box instead of the viewport, so for the length
   of the animation the island drops by exactly (document height - viewport
   height) and then springs back -- measured at 74px on a 390x844 viewport.
   gp-fade-up's translateY is what triggers it, which is why the glitch was
   direction-dependent: arriving at Home ran an opacity-only override, arriving
   at any other page ran gp-fade-up with its transform. Keep the fade, drop only
   the vertical travel; desktop has no island and keeps the original motion. */
@keyframes gp-fade-in {
    from {
        opacity: 0;
    }
    to {
        opacity: 1;
    }
}

/* Declared only on .gp-content-enter, never on the bare #main-content. The swap
   handler restarts the entrance by removing the class, forcing a reflow and
   re-adding it, and that only restarts an animation when the animation-name
   actually changes between the two states -- so keeping the base selector
   animation-free is what makes the fade replay on every swap. */
body.gp-has-floating-nav #main-content {
    animation: none;
}

body.gp-has-floating-nav #main-content.gp-content-enter {
    animation: gp-fade-in 0.38s ease;
}

body.gp-has-floating-nav #main-content.gp-content-exit {
    transform: none;
}

/* --- Profile completion ring ------------------------------------------------
 *
 * Progress arc drawn around the member avatar (mobile floating button and
 * desktop topbar chip). The arc fills clockwise from the top up to the
 * completion percentage, which rides the end of the arc as a small tag. A
 * complete profile has nothing left to nudge about, so at 100% no ring is drawn.
 */

/* Registered so the arc can sweep in on load; browsers without @property just
   show the final arc. */
@property --gp-ring-sweep {
    syntax: "<angle>";
    inherits: false;
    initial-value: 0deg;
}

.gp-avatar-ring {
    --gp-ring-thickness: 3px;
    --gp-ring-inset: 2px;
    --gp-ring-percent: 100;
    --gp-ring-angle: calc(var(--gp-ring-percent) * 3.6deg);
    --gp-ring-track: color-mix(in srgb, var(--gp-brand) 20%, transparent);
    --gp-ring-sweep-duration: 900ms;
    --gp-ring-sweep-easing: cubic-bezier(0.22, 1, 0.36, 1);
    position: relative;
    display: inline-flex;
    width: 3.25rem;
    height: 3.25rem;
    flex: none;
    border-radius: 999px;
}

/* The arc itself: a conic sweep masked down to an annulus, brand-coloured up
   to the completion angle over a faint track. */
.gp-avatar-ring::before {
    --gp-ring-sweep: var(--gp-ring-angle);
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background: conic-gradient(
        var(--gp-brand) 0deg var(--gp-ring-sweep),
        var(--gp-ring-track) var(--gp-ring-sweep) 360deg);
    -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - var(--gp-ring-thickness)), #000 calc(100% - var(--gp-ring-thickness)));
    mask: radial-gradient(farthest-side, transparent calc(100% - var(--gp-ring-thickness)), #000 calc(100% - var(--gp-ring-thickness)));
    animation: gp-ring-sweep var(--gp-ring-sweep-duration) var(--gp-ring-sweep-easing) 1;
}

/* Soft halo that breathes a few times on load while the profile is still
   incomplete, then settles — permanent motion in page chrome is distracting,
   and the avatar survives htmx swaps so this only replays on a full load. */
.gp-avatar-ring::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    box-shadow: 0 0 0 0 color-mix(in srgb, var(--gp-brand) 45%, transparent);
    animation: gp-ring-pulse 2.6s ease-out 3;
    pointer-events: none;
}

/* Complete: no arc and no halo, just the avatar filling the whole footprint. */
.gp-avatar-ring.is-complete::before,
.gp-avatar-ring.is-complete::after {
    content: none;
}

.gp-avatar-ring.is-complete .gp-avatar-ring-face {
    inset: 0;
}

.gp-avatar-ring-face {
    position: absolute;
    inset: calc(var(--gp-ring-thickness) + var(--gp-ring-inset));
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 999px;
    overflow: hidden;
    background: color-mix(in srgb, var(--gp-brand-pale) 60%, #ffffff);
    color: var(--gp-brand-deep);
    font-family: var(--gp-font-display);
    font-weight: 800;
    font-size: 1.05rem;
    letter-spacing: 0.02em;
}

.gp-avatar-ring-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* Percentage tag riding the end of the arc: the orbit turns to the arc's end
   angle and the tag turns back by the same amount, so its text stays upright.
   Both animate with the arc sweep, so the tag travels along as the arc fills. */
.gp-avatar-ring-tag-orbit {
    position: absolute;
    inset: 0;
    transform: rotate(var(--gp-ring-angle));
    animation: gp-ring-orbit var(--gp-ring-sweep-duration) var(--gp-ring-sweep-easing) 1;
    pointer-events: none;
}

.gp-avatar-ring-tag {
    position: absolute;
    top: calc(var(--gp-ring-thickness) / 2);
    left: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: 1rem;
    padding: 0 0.25rem;
    border-radius: 999px;
    border: 1.5px solid #ffffff;
    background: var(--gp-brand);
    color: #ffffff;
    font-size: 0.58rem;
    font-weight: 800;
    line-height: 1;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    transform: translate(-50%, -50%) rotate(calc(-1 * var(--gp-ring-angle)));
    animation: gp-ring-tag var(--gp-ring-sweep-duration) var(--gp-ring-sweep-easing) 1;
}

.gp-avatar-ring.is-sm {
    --gp-ring-thickness: 2px;
    --gp-ring-inset: 1.5px;
    width: 2.4rem;
    height: 2.4rem;
}

.gp-avatar-ring.is-sm .gp-avatar-ring-face {
    font-size: 0.8rem;
}

.gp-avatar-ring.is-sm .gp-avatar-ring-tag {
    height: 0.85rem;
    padding: 0 0.18rem;
    border-width: 1px;
    font-size: 0.5rem;
}

@keyframes gp-ring-sweep {
    from {
        --gp-ring-sweep: 0deg;
    }
}

@keyframes gp-ring-orbit {
    from {
        transform: rotate(0deg);
    }
}

@keyframes gp-ring-tag {
    from {
        transform: translate(-50%, -50%) rotate(0deg);
    }
}

@keyframes gp-ring-pulse {
    0% {
        box-shadow: 0 0 0 0 color-mix(in srgb, var(--gp-brand) 45%, transparent);
    }

    55%,
    100% {
        box-shadow: 0 0 0 7px color-mix(in srgb, var(--gp-brand) 0%, transparent);
    }
}

@media (prefers-reduced-motion: reduce) {
    .gp-avatar-ring::before,
    .gp-avatar-ring::after,
    .gp-avatar-ring-tag-orbit,
    .gp-avatar-ring-tag {
        animation: none;
    }
}

/* --- Attendee avatar chips --------------------------------------------------
 *
 * Rows in the event detail panel's sign-up / attendance lists: a photo (or
 * gradient-initials fallback when the member has none) plus their current
 * display name, staggered in on load and lifting slightly on hover.
 */

.gp-attendee-list {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}

.gp-attendee-chip {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.35rem 0.6rem;
    border-radius: 0.75rem;
    background: color-mix(in srgb, var(--gp-brand-pale) 45%, #ffffff);
    opacity: 0;
    transform: translateY(6px);
    animation: gp-chip-in 0.35s ease-out forwards;
    animation-delay: calc(var(--gp-chip-i, 0) * 45ms);
    transition: background-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
}

.gp-attendee-chip:hover {
    background: color-mix(in srgb, var(--gp-brand-pale) 75%, #ffffff);
    transform: translateY(-1px);
    box-shadow: 0 2px 8px color-mix(in srgb, var(--gp-brand) 18%, transparent);
}

.gp-attendee-chip-row {
    justify-content: space-between;
}

.gp-attendee-chip-avatar {
    flex: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    border-radius: 999px;
    overflow: hidden;
    background: linear-gradient(135deg, var(--gp-brand), var(--gp-brand-deep));
    color: #ffffff;
    font-family: var(--gp-font-display);
    font-weight: 800;
    font-size: 0.65rem;
    letter-spacing: 0.02em;
}

.gp-attendee-chip-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.gp-attendee-chip-name {
    font-size: 0.8rem;
    font-weight: 600;
    flex: 1;
}

.gp-attendee-chip-time {
    flex: none;
    font-size: 0.7rem;
    opacity: 0.6;
}

@keyframes gp-chip-in {
    from {
        opacity: 0;
        transform: translateY(6px);
    }

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

@media (prefers-reduced-motion: reduce) {
    .gp-attendee-chip {
        opacity: 1;
        transform: none;
        animation: none;
    }
}

/* Settings fields the completion ring deep-links into: flash once on arrival
   so the member sees which field the nudge meant. */
.gp-target-highlight {
    border-radius: 1.5rem;
    scroll-margin-top: 5rem;
}

.gp-target-highlight:target {
    animation: gp-target-flash 2.4s ease-out 1;
}

@keyframes gp-target-flash {
    0%,
    70% {
        box-shadow: 0 0 0 4px color-mix(in srgb, var(--gp-brand) 35%, transparent);
    }

    100% {
        box-shadow: 0 0 0 4px color-mix(in srgb, var(--gp-brand) 0%, transparent);
    }
}

@media (prefers-reduced-motion: reduce) {
    .gp-target-highlight:target {
        animation: none;
        box-shadow: 0 0 0 3px color-mix(in srgb, var(--gp-brand) 35%, transparent);
    }
}

/* Desktop topbar avatar button replacing the plain username pill. */
.gp-topbar-avatar {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.25rem 0.85rem 0.25rem 0.25rem;
    border-radius: 999px;
    border: 1px solid color-mix(in srgb, var(--gp-brand) 22%, transparent);
    background: #ffffff;
    color: var(--gp-brand-deep);
    font-weight: 700;
    font-size: 0.8rem;
    line-height: 1.1;
    max-width: 12rem;
    transition: border-color 0.16s ease, box-shadow 0.16s ease;
}

.gp-topbar-avatar:hover,
.gp-topbar-avatar:focus-visible {
    border-color: color-mix(in srgb, var(--gp-brand) 45%, transparent);
    box-shadow: 0 6px 18px color-mix(in srgb, var(--gp-brand) 18%, transparent);
}

.gp-topbar-avatar-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.gp-floating-avatar {
    position: fixed;
    top: calc(env(safe-area-inset-top, 0px) + 3.25rem);
    right: 0.85rem;
    z-index: 70;
}

.gp-floating-avatar-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 0;
    border-radius: 999px;
    background: transparent;
    /* filter (not box-shadow) so the drop shadow follows the ring, not a box. */
    filter: drop-shadow(0 10px 18px rgba(15, 23, 42, 0.28));
    transition: transform 0.16s ease, filter 0.16s ease;
}

.gp-floating-avatar-btn:hover,
.gp-floating-avatar-btn:focus-visible {
    transform: translateY(-1px);
    filter: drop-shadow(0 14px 22px rgba(15, 23, 42, 0.32));
}

/* The floating avatar sits over page content, so its face keeps the glassy
   backdrop the old button had. */
.gp-floating-avatar-btn .gp-avatar-ring-face {
    background: color-mix(in srgb, var(--gp-brand-pale) 45%, rgba(255, 255, 255, 0.88));
    backdrop-filter: blur(14px) saturate(1.6);
    -webkit-backdrop-filter: blur(14px) saturate(1.6);
}

/* Without a ring, keep the white rim the floating button always had. */
.gp-floating-avatar-btn .gp-avatar-ring.is-complete .gp-avatar-ring-face {
    border: 2px solid rgba(255, 255, 255, 0.85);
}

.gp-profile-nudge-title {
    padding-bottom: 0.15rem;
    font-size: 0.7rem;
    font-weight: 800;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--gp-brand-deep);
}

.gp-profile-nudge-item {
    align-items: center;
    gap: 0.55rem;
    color: var(--gp-brand-deep);
}

.gp-profile-nudge-separator {
    height: 1px;
    margin: 0.35rem 0.5rem;
    background: color-mix(in srgb, var(--gp-brand) 16%, transparent);
}

.gp-profile-nudge-dot {
    width: 0.55rem;
    height: 0.55rem;
    flex: none;
    border-radius: 999px;
    border: 2px solid var(--gp-brand);
}

.gp-profile-nudge-gain {
    justify-self: end;
    margin-left: auto;
    font-size: 0.7rem;
    font-weight: 800;
    color: var(--gp-brand);
    font-variant-numeric: tabular-nums;
}

/* Settings page note listing what the completion ring is still missing. */
.gp-profile-note {
    display: flex;
    align-items: flex-start;
    gap: 0.85rem;
    padding: 1rem 1.25rem;
    border: 1px solid color-mix(in srgb, var(--gp-brand) 22%, transparent);
    border-radius: 1.75rem;
    background: color-mix(in srgb, var(--gp-brand) 7%, white);
    color: var(--gp-brand-deep);
}

.gp-profile-note-icon {
    width: 1.35rem;
    height: 1.35rem;
    flex: none;
    margin-top: 0.1rem;
    color: var(--gp-brand);
}

.gp-profile-note-body {
    min-width: 0;
    flex: 1;
}

.gp-profile-note-title {
    font-weight: 800;
}

.gp-profile-note-text {
    margin-top: 0.1rem;
    font-size: 0.875rem;
}

.gp-profile-note-list {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-top: 0.65rem;
}

.gp-profile-note-link {
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
    padding: 0.4rem 0.85rem;
    border: 1px solid color-mix(in srgb, var(--gp-brand) 25%, transparent);
    border-radius: 999px;
    background: white;
    font-size: 0.875rem;
    font-weight: 600;
    color: var(--gp-brand-deep);
    transition: border-color 0.15s ease;
}

.gp-profile-note-link:hover,
.gp-profile-note-link:focus-visible {
    border-color: var(--gp-brand);
}

.gp-profile-note-link .gp-profile-nudge-gain {
    margin-left: 0.15rem;
}

.gp-floating-avatar-menu {
    margin-top: 0.6rem;
}

.gp-floating-avatar-menu-item {
    display: flex;
    width: 100%;
    text-align: left;
    font-weight: 600;
}

.gp-floating-avatar-logout {
    color: #dc2626;
}

.gp-floating-avatar-logout:hover {
    color: #b91c1c;
    background: rgba(220, 38, 38, 0.08);
}

.gp-floating-bottom-nav {
    position: fixed;
    left: 50%;
    bottom: calc(env(safe-area-inset-bottom, 0px) + 1.1rem);
    transform: translateX(-50%);
    z-index: 70;
    width: max-content;
    max-width: calc(100vw - 1.6rem);
}

.gp-floating-bottom-nav-inner {
    display: flex;
    align-items: center;
    gap: 0.2rem;
    padding: 0.35rem;
    border-radius: 1.6rem;
    background: rgba(15, 23, 42, 0.78);
    border: 1px solid rgba(255, 255, 255, 0.14);
    backdrop-filter: blur(20px) saturate(1.7);
    -webkit-backdrop-filter: blur(20px) saturate(1.7);
    box-shadow: 0 20px 44px rgba(15, 23, 42, 0.38), 0 2px 10px rgba(15, 23, 42, 0.28);
}

.gp-floating-nav-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.15rem;
    min-width: 3.9rem;
    max-width: 5.5rem;
    height: 3.4rem;
    padding: 0 0.4rem;
    border-radius: 1.25rem;
    color: rgba(255, 255, 255, 0.72);
    transition: background-color 0.16s ease, color 0.16s ease, transform 0.16s ease;
}

/* The label names the destination so the icon is never the only cue. The
   anchor's aria-label already provides the accessible name, so the visible
   text is marked presentational to avoid it being announced twice. */
.gp-floating-nav-label {
    font-size: 0.625rem;
    font-weight: 700;
    line-height: 1;
    letter-spacing: 0.01em;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.gp-floating-nav-item:hover {
    background: rgba(255, 255, 255, 0.14);
    color: #fff;
}

/* Uses the -dark step rather than the base: at the base step's lightness
   (L 0.59) near-white text lands around 3.8:1, below AA for this small nav
   label. The -dark step (L 0.526) holds >=5:1 for every brand hue. */
.gp-floating-nav-item.is-active-page {
    background: var(--gp-brand-dark);
    color: #f8fafc;
    box-shadow: 0 6px 18px color-mix(in srgb, var(--gp-brand) 55%, transparent);
}

.gp-floating-nav-item svg {
    width: 1.25rem;
    height: 1.25rem;
    flex: none;
}

/* On the narrowest phones four labelled items would overflow the island, so
   trade padding and label size for fit rather than letting it clip. */
@media (max-width: 360px) {
    .gp-floating-nav-item {
        min-width: 3.35rem;
        padding: 0 0.2rem;
    }

    .gp-floating-nav-label {
        font-size: 0.5625rem;
    }
}

/* Castle design status visuals + drag-and-drop reorder (shared by the admin
   event panel, the calendar event detail modal, and the my-assignment
   page). */
.gp-design-row {
    position: relative;
    transition: opacity 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
}

.gp-design-row-in-progress {
    box-shadow: 0 0 0 2px #16a34a inset;
    background-color: rgba(22, 163, 74, 0.06);
}

.gp-design-progress-tag {
    position: absolute;
    top: -0.55rem;
    left: 0.5rem;
    background: #16a34a;
    color: #fff;
    font-size: 0.6rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.02em;
    padding: 0.05rem 0.4rem;
    border-radius: 999px;
    line-height: 1.3;
    z-index: 1;
}

.gp-design-row-done {
    opacity: 0.55;
    filter: grayscale(35%);
}

.gp-design-drag-handle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.15rem;
    height: 1.15rem;
    cursor: grab;
    color: rgba(100, 116, 139, 0.8);
    touch-action: none;
    user-select: none;
    -webkit-user-select: none;
    flex-shrink: 0;
}

.gp-design-drag-handle:active {
    cursor: grabbing;
}

[data-design-reorder-list] .gp-design-row {
    cursor: default;
    /* pan-y (not none): the browser only honors touch-action as set at the
       very start of a touch, before any movement — it can't be flipped to
       none once a scroll has begun, and starting none here would block
       every scroll swipe that happens to begin on a card. Drag-and-drop
       therefore only starts from .gp-design-drag-handle (touch-action:
       none, see above), a small dedicated surface with no scroll ambiguity
       of its own; the row stays natively scrollable everywhere else. */
    touch-action: pan-y;
}

/* Dragging a row starts from a pointerdown on its handle, but the pointer
   inevitably passes over sibling text (name, badges) while moving; without
   this the browser starts a text selection in parallel with the reorder
   drag on some browsers even though pointerdown's default is prevented. */
.gp-design-list[data-design-reorder-list],
[data-design-reorder-list] .gp-design-row {
    -webkit-user-select: none;
    user-select: none;
}

.gp-design-row.gp-design-row-dragging {
    opacity: 0.3;
}

.gp-design-row.gp-design-row-drop-before {
    box-shadow: inset 0 2px 0 0 var(--gp-brand);
}

.gp-design-row.gp-design-row-drop-after {
    box-shadow: inset 0 -2px 0 0 var(--gp-brand);
}

/* Floating clone that tracks the pointer while dragging a design row;
   pointer-events:none so elementFromPoint sees the row underneath it
   instead of the ghost itself, which is needed for drop-target detection. */
.gp-design-row-ghost {
    position: fixed;
    margin: 0;
    pointer-events: none;
    opacity: 1;
    background-color: var(--fallback-b1, oklch(var(--b1)));
    box-shadow: 0 8px 20px rgba(15, 23, 42, 0.3);
    transform: rotate(1.5deg) scale(1.03);
    z-index: 1000;
    cursor: grabbing;
}

.gp-design-rename {
    flex-shrink: 0;
}

.gp-design-rename-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.15rem;
    height: 1.15rem;
    font-size: 0.75rem;
    line-height: 1;
    cursor: pointer;
    list-style: none;
    color: rgba(100, 116, 139, 0.8);
}

.gp-design-rename-toggle::-webkit-details-marker {
    display: none;
}

.gp-design-rename-form {
    margin-top: 0.3rem;
}

/* daisyUI keeps closed <dialog class="modal"> elements rendered as invisible
   full-viewport overlays (display:grid + opacity:0) to animate open/close.
   The admin panel holds one event-edit dialog per event, and each contains a
   lazily loaded members panel with hx-trigger="intersect once" — with the
   daisyUI behavior, every closed dialog "intersects" the viewport as soon as
   the Events tab shows, firing all panel fetches at once. Closed event-edit
   dialogs must therefore be display:none (the native <dialog> default) so a
   panel only loads when its modal is actually opened. Costs the fade
   animation for these modals only. */
dialog[id^="modal-update-event-"]:not([open]) {
    display: none;
}

/* ---------------------------------------------------------------------------
 * Brand utility classes.
 *
 * Tailwind palette utilities (text-sky-700, bg-sky-50, ...) are compiled to
 * literal hex at build time and therefore CANNOT follow --gp-brand. Any surface
 * that should wear the colla's color must use these instead. They are the only
 * supported way to apply the brand from a template.
 *
 * Semantic status utilities (text-red-700, bg-green-50, ...) are still correct
 * to use directly: those must NOT follow the brand.
 * ------------------------------------------------------------------------- */
.gp-brand-ink {
    color: var(--gp-brand-dark);
}

.gp-brand-ink-strong {
    color: var(--gp-brand-deep);
}

.gp-brand-ink-deepest {
    color: color-mix(in srgb, var(--gp-brand-deep) 78%, #000000);
}

/* Pale tinted stat tile: pair with Tailwind's `border` for the 1px width. */
.gp-brand-tile {
    border-color: var(--gp-brand-soft);
    background: color-mix(in srgb, var(--gp-brand-pale) 40%, #ffffff);
}

/* Large washed panel (admin hero). */
.gp-brand-panel {
    background: linear-gradient(
        to bottom right,
        #ffffff 0%,
        color-mix(in srgb, var(--gp-brand-pale) 28%, #ffffff) 50%,
        color-mix(in srgb, var(--gp-brand-pale) 62%, #ffffff) 100%
    );
}

/* ---------------------------------------------------------------------------
 * Event type: assaig vs diada.
 *
 * Both stay on the brand hue -- a colla configures one color and these follow.
 * The distinction is WEIGHT, not hue: an assaig is the everyday default and
 * wears a light outline; a diada is the standout occasion and wears a solid
 * fill. That reads at a glance in a mixed event list without spending a second
 * hue, and it survives rebranding to any color.
 *
 * These previously used badge-accent vs badge-secondary, which were an orange
 * and an indigo from DaisyUI's stock emerald theme. Centering the palette on
 * the brand made both blue and flattened the distinction; this restores it.
 * ------------------------------------------------------------------------- */
.badge.gp-event-assaig,
.btn.gp-event-assaig {
    background: transparent;
    border: 1px solid var(--gp-brand-soft);
    color: var(--gp-brand-deep);
}

.btn.gp-event-assaig:hover {
    background: color-mix(in srgb, var(--gp-brand-pale) 55%, #ffffff);
    border-color: var(--gp-brand-dark);
}

/* Solid: --gp-brand-dark has a pinned lightness of 0.526, so white text holds
   >=5:1 on it for every possible brand hue. */
.badge.gp-event-diada,
.btn.gp-event-diada {
    background: var(--gp-brand-dark);
    border: 1px solid var(--gp-brand-dark);
    color: #ffffff;
}

.btn.gp-event-diada:hover {
    background: var(--gp-brand-deep);
    border-color: var(--gp-brand-deep);
}
