/* ============================================================================
 * Journey Modal — full V0 fidelity (components/journey-modal.tsx).
 *
 * Three views inside one panel: select / register / login. Shared chrome:
 * gilded title strip with fade edges, sword divider, gold corner brackets,
 * top + bottom shimmer hairlines, blurred radial backdrop, glassmorphic
 * panel with gold-tinted gradient + inset glow.
 * ========================================================================== */

/* ─── Root + backdrop ───────────────────────────────────────────────────── */

.journey-root {
    position: fixed;
    inset: 0;
    z-index: 900;
}
.journey-backdrop {
    position: absolute;
    inset: 0;
    background: radial-gradient(ellipse at 50% 30%, rgba(15,10,5,0.55) 0%, rgba(0,0,0,0.68) 100%);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
}
.journey-modal {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
    z-index: 901;
}
.journey-modal-frame {
    position: relative;
    width: 95vw;
    max-width: 72rem;          /* V0: max-w-6xl = 72rem */
    max-height: 90vh;
    display: flex;
    flex-direction: column;
}

/* ─── Outer glow ring + panel ────────────────────────────────────────────── */

.journey-glow-ring {
    position: absolute;
    inset: -0.25rem;
    border-radius: 1rem;
    opacity: 0.5;
    filter: blur(40px);
    pointer-events: none;
    background: linear-gradient(135deg,
        rgba(212,175,55,0.25) 0%,
        transparent 40%,
        rgba(212,175,55,0.15) 100%);
}
.journey-panel {
    position: relative;
    overflow: hidden;
    border-radius: 1rem;
    border: 1px solid rgba(212,175,55,0.28);
    background: linear-gradient(170deg,
        rgba(22,16,10,0.82) 0%,
        rgba(12,9,6,0.88) 60%,
        rgba(18,13,8,0.85) 100%);
    backdrop-filter: blur(24px);
    -webkit-backdrop-filter: blur(24px);
    box-shadow:
        0 40px 100px rgba(0,0,0,0.6),
        0 0 0 1px rgba(212,175,55,0.06),
        inset 0 1px 0 rgba(212,175,55,0.2),
        inset 0 -1px 0 rgba(0,0,0,0.4);
    display: flex;
    flex-direction: column;
    min-height: 0;
}

/* ─── Top + bottom shimmer hairlines ─────────────────────────────────────── */

.journey-shimmer-top,
.journey-shimmer-bottom {
    position: absolute;
    right: 0;
    left: 0;
    pointer-events: none;
}
.journey-shimmer-top {
    top: 0;
    height: 2px;
    background: linear-gradient(90deg,
        transparent 0%,
        #D4AF37 20%,
        #FFF8DC 50%,
        #D4AF37 80%,
        transparent 100%);
    box-shadow: 0 0 24px rgba(212,175,55,0.7);
}
.journey-shimmer-bottom {
    bottom: 0;
    height: 1px;
    background: linear-gradient(90deg,
        transparent,
        rgba(212,175,55,0.25) 30%,
        rgba(212,175,55,0.4) 50%,
        rgba(212,175,55,0.25) 70%,
        transparent);
}

/* ─── Corner brackets ────────────────────────────────────────────────────── */

.journey-corner {
    position: absolute;
    width: 1.25rem;
    height: 1.25rem;
    pointer-events: none;
}
.journey-corner.tl { top: 1rem; right: 1rem;
    border-top:    1px solid rgba(212,175,55,0.35);
    border-right:   1px solid rgba(212,175,55,0.35);
    border-top-right-radius: 4px; }
.journey-corner.tr { top: 1rem; left: 1rem;
    border-top:    1px solid rgba(212,175,55,0.35);
    border-left:  1px solid rgba(212,175,55,0.35);
    border-top-left-radius: 4px; }
.journey-corner.bl { bottom: 1rem; right: 1rem;
    border-bottom: 1px solid rgba(212,175,55,0.35);
    border-right:   1px solid rgba(212,175,55,0.35);
    border-bottom-right-radius: 4px; }
.journey-corner.br { bottom: 1rem; left: 1rem;
    border-bottom: 1px solid rgba(212,175,55,0.35);
    border-left:  1px solid rgba(212,175,55,0.35);
    border-bottom-left-radius: 4px; }

/* ─── Close (red circular X, top-right) ──────────────────────────────────── */

.journey-close {
    position: absolute;
    top: 1.25rem;
    left: 1.25rem;
    width: 2.25rem;
    height: 2.25rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 9999px;
    background: linear-gradient(135deg, rgba(90,30,30,0.9), rgba(50,15,15,0.95));
    border: 1px solid rgba(200,60,60,0.35);
    box-shadow: 0 4px 14px rgba(0,0,0,0.5);
    color: #FCA5A5;
    cursor: pointer;
    transition: transform 200ms ease, filter 200ms ease;
    z-index: 5;
}
.journey-close:hover {
    transform: scale(1.10);
    filter: brightness(1.1);
}

/* ─── Scroll body ────────────────────────────────────────────────────────── */

.journey-scroll {
    overflow-y: auto;
    overflow-x: hidden;
    flex: 1 1 auto;
    min-height: 0;
    padding: 1.5rem 1.25rem 1.25rem;
    scrollbar-width: thin;
    scrollbar-color: rgba(212,175,55,0.25) transparent;
}
.journey-scroll::-webkit-scrollbar { width: 8px; }
.journey-scroll::-webkit-scrollbar-track { background: transparent; }
.journey-scroll::-webkit-scrollbar-thumb {
    background: rgba(212,175,55,0.2);
    border-radius: 4px;
}
@media (min-width: 768px) {
    .journey-scroll { padding: 1.5rem 2rem 1.25rem; }
}

/* ─── Gilded title strip ─────────────────────────────────────────────────── */

.journey-header { text-align: center; margin-bottom: 2.5rem; }
.journey-title-wrap {
    position: relative;
    display: inline-block;
    padding: 0.625rem 2.5rem;
}
.journey-title-fade {
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: linear-gradient(90deg,
        transparent 0%,
        rgba(8,5,2,0.82) 28%,
        rgba(8,5,2,0.88) 50%,
        rgba(8,5,2,0.82) 72%,
        transparent 100%);
}
.journey-title-line {
    position: absolute;
    right: 0;
    left: 0;
    height: 1px;
    pointer-events: none;
    background: linear-gradient(90deg,
        transparent 0%,
        rgba(212,175,55,0.28) 35%,
        rgba(212,175,55,0.28) 65%,
        transparent 100%);
}
.journey-title-line.top    { top: 0; }
.journey-title-line.bottom { bottom: 0; }
.journey-title {
    position: relative;
    margin: 0;
    font-family: 'Cinzel', serif;
    font-size: 1.25rem;
    font-weight: 700;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    background: linear-gradient(180deg,
        #FFF8E7 0%,
        #F4E287 30%,
        #D4AF37 65%,
        #9A7515 100%);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    filter: drop-shadow(0 2px 14px rgba(212,175,55,0.35));
}
@media (min-width: 768px) {
    .journey-title { font-size: 1.5rem; }
}

/* ─── Sword divider ──────────────────────────────────────────────────────── */

.journey-divider {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 1rem;
    margin-top: 1rem;
}
.journey-divider.center-only { margin: 1rem 0 1.25rem; }
.journey-divider .line {
    flex: 1 1 0;
    max-width: 100px;
    height: 1px;
    background: linear-gradient(90deg, transparent, rgba(212,175,55,0.5));
}
.journey-divider .line.right {
    background: linear-gradient(270deg, transparent, rgba(212,175,55,0.5));
}
.journey-divider-glyph {
    display: inline-flex;
    color: rgba(212,175,55,0.7);
    transform: rotate(90deg);
}

/* ─── Back row (register + login views) ──────────────────────────────────── */

.journey-back-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: 1.25rem;
}
.journey-back {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.75rem;
    border-radius: 0.5rem;
    background: rgba(212,175,55,0.08);
    border: 1px solid rgba(212,175,55,0.25);
    color: #D4AF37;
    cursor: pointer;
    font-family: 'Cinzel', serif;
    font-size: 0.8125rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    transition: transform 200ms ease;
    flex-shrink: 0;
}
.journey-back:hover { transform: scale(1.05); }
/*
 * A counterweight so the title centres against the back button opposite it.
 * It was flex-shrink: 0, which made the back row 33px wider than a 375px
 * viewport and clipped the right edge of the modal. It gives way now when there
 * is no room, and the title simply centres a little less perfectly there.
 */
.journey-back-spacer { flex: 0 1 5.5rem; min-width: 0; }

/* ─── View 1: server grid (3 cards) ──────────────────────────────────────── */

.journey-server-grid {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 1.25rem;
    align-items: stretch;
}
.journey-server-grid > .jmcard {
    /* 1-card row: card sits at its natural width, centered.
       2-card row: each takes a balanced share, capped so the pair stays centered.
       3-card row: cards distribute evenly across the panel width.
       Wraps below 700px panel width to a single column. */
    flex: 1 1 280px;
    max-width: 360px;
}
@media (max-width: 700px) {
    .journey-server-grid > .jmcard { max-width: 100%; }
}

/* Prime realm sits slightly ELEVATED above its neighbours (desktop rows
   only — in the single-column mobile stack all cards sit flush). */
@media (min-width: 701px) {
    .journey-server-grid .jmcard--prime { transform: translateY(-0.75rem); }
    .journey-server-grid .jmcard--prime:hover { transform: translateY(-1.125rem); }
}

/* ─── Journey-Modal Server Card (jmcard) — V0-faithful ──────────────────── */

.jmcard {
    position: relative;
    display: flex;
    flex-direction: column;
    width: 100%;
    align-self: stretch;
    overflow: hidden;
    border-radius: 0.75rem;
    background: linear-gradient(165deg, rgba(38,28,18,0.72) 0%, rgba(18,14,9,0.80) 100%);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border: 1px solid rgba(212,175,55,0.25);
    box-shadow:
        0 8px 32px rgba(0,0,0,0.4),
        inset 0 1px 0 rgba(212,175,55,0.1);
    transition: transform 300ms cubic-bezier(0.25, 0.46, 0.45, 0.94),
                border-color 300ms cubic-bezier(0.25, 0.46, 0.45, 0.94),
                box-shadow 300ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.jmcard:hover {
    transform: translateY(-6px);
    border-color: rgba(212,175,55,0.6);
    box-shadow:
        0 8px 30px rgba(217,119,6,0.15),
        0 24px 60px rgba(0,0,0,0.5),
        inset 0 1px 0 rgba(212,175,55,0.18);
}

/* Prime variant — amber tint + halo */
.jmcard--prime {
    border-color: rgba(245,158,11,0.55);
    box-shadow:
        0 0 20px rgba(245,158,11,0.12),
        0 8px 32px rgba(0,0,0,0.45),
        inset 0 1px 0 rgba(245,158,11,0.14);
}
/* Static soft halo — matches the toned-down landing prime card. The old
   infinite "breathe" animation (blur 80px pulsing forever) was both the
   exaggerated-glow complaint and a constant GPU cost. */
.jmcard--prime::before {
    content: '';
    position: absolute;
    inset: -0.5rem;
    z-index: -1;
    border-radius: 1rem;
    pointer-events: none;
    background: radial-gradient(ellipse at 50% 60%,
        rgba(245,158,11,0.16) 0%,
        rgba(212,175,55,0.07) 50%,
        transparent 80%);
    filter: blur(36px);
    opacity: 0.4;
    transition: opacity 500ms ease;
}
.jmcard--prime:hover::before { opacity: 0.55; }
.jmcard--prime:hover {
    border-color: rgba(245,158,11,0.65);
    box-shadow:
        0 8px 30px rgba(217,119,6,0.14),
        0 24px 60px rgba(0,0,0,0.55),
        inset 0 1px 0 rgba(245,158,11,0.2);
}

/* ─── Launching variant (countdown in progress) ─────────────────────────── */

.jmcard--launching {
    border-color: rgba(59, 130, 246, 0.45);
    background: linear-gradient(165deg, rgba(20, 28, 48, 0.78) 0%, rgba(10, 14, 26, 0.88) 100%);
    box-shadow:
        0 0 22px rgba(59, 130, 246, 0.10),
        0 8px 32px rgba(0, 0, 0, 0.45),
        inset 0 1px 0 rgba(96, 165, 250, 0.12);
}
.jmcard--launching:hover {
    border-color: rgba(59, 130, 246, 0.7);
    box-shadow:
        0 8px 30px rgba(59, 130, 246, 0.20),
        0 24px 60px rgba(0, 0, 0, 0.5),
        inset 0 1px 0 rgba(96, 165, 250, 0.20);
}

/* Blue top hairline replaces the gold one on launching cards */
.jmcard-top-hair--blue {
    background: linear-gradient(90deg, transparent, rgba(96, 165, 250, 0.55), transparent) !important;
}

/* Launching status pill — blue, slow pulse, sits in the top-right corner */
.jmcard-status-pill--launching {
    flex-shrink: 0;
    margin-top: 0.25rem;
    padding: 0.125rem 0.625rem;
    border-radius: 9999px;
    background: rgba(59, 130, 246, 0.12);
    border: 1px solid rgba(59, 130, 246, 0.4);
    color: #93C5FD;
    font-family: 'Cinzel', serif;
    font-size: 0.6875rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    animation: jmcardLaunchingPulse 2.6s ease-in-out infinite;
}
@keyframes jmcardLaunchingPulse {
    0%, 100% {
        box-shadow: 0 0 6px rgba(59, 130, 246, 0.25), inset 0 0 0 0 rgba(59, 130, 246, 0);
    }
    50% {
        box-shadow: 0 0 14px rgba(59, 130, 246, 0.55), inset 0 0 8px rgba(59, 130, 246, 0.10);
    }
}

/* Override the static dot for the launching state — blue with subtle pulse */
.jmcard-livedot.is-launching .jmcard-livedot-core {
    background: #60A5FA;
    box-shadow: 0 0 6px rgba(96, 165, 250, 0.55);
    animation: jmcardLaunchingDot 2.6s ease-in-out infinite;
}
@keyframes jmcardLaunchingDot {
    0%, 100% { opacity: 0.7; }
    50%      { opacity: 1; }
}

/* Info pills used on launching cards (map size, speed) */
.jmcard-pill-info {
    background: rgba(96, 165, 250, 0.08);
    border: 1px solid rgba(96, 165, 250, 0.32);
    color: #BFDBFE;
}

/* Blue divider variant */
.jmcard-divider--blue {
    background: linear-gradient(90deg, transparent, rgba(59, 130, 246, 0.25), transparent) !important;
}

/* The countdown block (replaces the 3-stat grid on launching cards) */
.jmcard-countdown {
    height: 5.5rem;
    margin-bottom: 1.25rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.3rem;
    padding: 0.5rem;
    border-radius: 0.5rem;
    background:
        radial-gradient(ellipse at 50% 60%, rgba(59, 130, 246, 0.08) 0%, transparent 75%),
        rgba(0, 0, 0, 0.22);
    border: 1px solid rgba(96, 165, 250, 0.22);
}
.jmcard-countdown-lbl {
    font-family: 'Cinzel', serif;
    font-size: 0.8125rem;
    font-weight: 700;
    letter-spacing: 0.3em;
    text-transform: uppercase;
    color: rgba(147, 197, 253, 0.8);
}
.jmcard-countdown-val {
    font-family: 'Cinzel', serif;
    font-size: 1.625rem;
    font-weight: 700;
    line-height: 1;
    letter-spacing: 0.04em;
    font-variant-numeric: tabular-nums;
    color: #DBEAFE;
    text-shadow: 0 0 14px rgba(96, 165, 250, 0.55), 0 0 28px rgba(59, 130, 246, 0.22);
    min-width: 9ch;
    text-align: center;
}
.jmcard-countdown-val.is-tba {
    font-size: 1.125rem;
    color: rgba(191, 219, 254, 0.7);
    text-shadow: none;
    letter-spacing: 0.18em;
}

/* ─── SOON variant (announced world, no launch date yet) ────────────────── */

.jmcard--soon {
    border-color: rgba(239, 68, 68, 0.4);
    background: linear-gradient(165deg, rgba(44, 16, 14, 0.78) 0%, rgba(20, 8, 7, 0.88) 100%);
    box-shadow:
        0 0 22px rgba(239, 68, 68, 0.08),
        0 8px 32px rgba(0, 0, 0, 0.45),
        inset 0 1px 0 rgba(248, 113, 113, 0.12);
}
.jmcard--soon:hover {
    border-color: rgba(239, 68, 68, 0.6);
    box-shadow:
        0 8px 30px rgba(239, 68, 68, 0.16),
        0 24px 60px rgba(0, 0, 0, 0.5),
        inset 0 1px 0 rgba(248, 113, 113, 0.18);
}
.jmcard-top-hair--red {
    background: linear-gradient(90deg, transparent, rgba(248, 113, 113, 0.55), transparent) !important;
}
.jmcard-status-pill--soon {
    flex-shrink: 0;
    margin-top: 0.25rem;
    padding: 0.125rem 0.625rem;
    border-radius: 9999px;
    background: rgba(239, 68, 68, 0.12);
    border: 1px solid rgba(239, 68, 68, 0.4);
    color: #FCA5A5;
    font-family: 'Cinzel', serif;
    font-size: 0.6875rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.1em;
}
.jmcard-pill-info--red {
    background: rgba(239, 68, 68, 0.08) !important;
    border-color: rgba(248, 113, 113, 0.35) !important;
    color: #FECACA !important;
}
.jmcard-divider--red {
    background: linear-gradient(90deg, transparent, rgba(239, 68, 68, 0.28), transparent) !important;
}
/* The SOON block mirrors the countdown block's silhouette (same height) so
   a soon card lines up with launching/live cards in the grid. */
.jmcard-soonblock {
    height: 5.5rem;
    margin-bottom: 1.25rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.3rem;
    padding: 0.5rem;
    border-radius: 0.5rem;
    background:
        radial-gradient(ellipse at 50% 60%, rgba(239, 68, 68, 0.08) 0%, transparent 75%),
        rgba(0, 0, 0, 0.22);
    border: 1px solid rgba(239, 68, 68, 0.22);
}
.jmcard-soonblock-lbl {
    font-family: 'Cinzel', serif;
    font-size: 0.8125rem;
    font-weight: 700;
    letter-spacing: 0.35em;
    text-transform: uppercase;
    color: rgba(252, 165, 165, 0.7);
}
.jmcard-soonblock-val {
    font-family: 'Cinzel', serif;
    font-size: 1.625rem;
    font-weight: 700;
    line-height: 1;
    letter-spacing: 0.22em;
    color: #FEE2E2;
    text-shadow: 0 0 14px rgba(248, 113, 113, 0.5), 0 0 28px rgba(239, 68, 68, 0.2);
}
.jmcard--soon .jmcard-subtitle { color: #FECACA; }
.jmcard--soon .jmcard-name { color: rgba(252, 165, 165, 0.55); }
.jmcard--soon .jmcard-livedot .jmcard-livedot-core {
    background: #F87171;
    box-shadow: 0 0 6px rgba(248, 113, 113, 0.5);
}
/* Discord CTA on soon cards — outlined, quieter than the gold Register. */
.jmcard-btn-discord {
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: rgba(88, 101, 242, 0.12);
    color: #C7D2FE;
    border-color: rgba(88, 101, 242, 0.45);
    font-weight: 700;
}
.jmcard-btn-discord:hover {
    background: rgba(88, 101, 242, 0.22);
    border-color: rgba(88, 101, 242, 0.65);
    color: #E0E7FF;
}

/* ─── DEV variant (maintenance) — readable card, slate badge ────────────── */

.jmcard--dev {
    border-color: rgba(100, 116, 139, 0.35);
    background: linear-gradient(165deg, rgba(24, 28, 34, 0.78) 0%, rgba(12, 14, 18, 0.88) 100%);
    box-shadow:
        0 8px 32px rgba(0, 0, 0, 0.45),
        inset 0 1px 0 rgba(148, 163, 184, 0.1);
}
.jmcard--dev:hover {
    transform: none;
    border-color: rgba(100, 116, 139, 0.5);
}
.jmcard-top-hair--slate {
    background: linear-gradient(90deg, transparent, rgba(148, 163, 184, 0.45), transparent) !important;
}
.jmcard-status-pill--dev {
    flex-shrink: 0;
    margin-top: 0.25rem;
    padding: 0.125rem 0.625rem;
    border-radius: 9999px;
    background: rgba(100, 116, 139, 0.14);
    border: 1px solid rgba(100, 116, 139, 0.4);
    color: #CBD5E1;
    font-family: 'Cinzel', serif;
    font-size: 0.6875rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.1em;
}
.jmcard-divider--slate {
    background: linear-gradient(90deg, transparent, rgba(148, 163, 184, 0.22), transparent) !important;
}
.jmcard-devblock {
    height: 5.5rem;
    margin-bottom: 1.25rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.25rem;
    padding: 0.5rem;
    border-radius: 0.5rem;
    background: rgba(0, 0, 0, 0.22);
    border: 1px solid rgba(100, 116, 139, 0.22);
    color: rgba(148, 163, 184, 0.8);
}
.jmcard-devblock-text {
    font-family: 'Cinzel', serif;
    font-size: 0.8125rem;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: #CBD5E1;
}
.jmcard-devblock-sub {
    font-family: 'Inter', sans-serif;
    /* Raised 0.6875rem -> 0.75rem to sit on the same caption tier as
       .jmcard-stat-lbl. This is a sentence, not a badge: the badge tier
       (0.6875rem) is for short uppercase status pills, which this is not. */
    font-size: 0.75rem;
    color: rgba(174, 189, 206, 0.92);
}
.jmcard-btn-unavailable {
    cursor: not-allowed;
    user-select: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* 0.55 alpha measured 3.03:1. A disabled control is exempt from the
       contrast floor, but this one still has to say WHY the card cannot be
       used, so it is lifted to a readable level while staying visibly inert
       against the two live cards next to it. */
    background: rgba(100, 116, 139, 0.14);
    color: rgba(186, 199, 214, 0.86);
    border-color: rgba(100, 116, 139, 0.38);
    font-weight: 700;
    letter-spacing: 0.1em;
}
.jmcard-btn-unavailable:hover { transform: none; filter: none; }

/* Sealed variant (Coming Soon) */
.jmcard--sealed {
    background: linear-gradient(165deg, rgba(14,11,8,0.75) 0%, rgba(8,6,4,0.85) 100%);
    border-color: rgba(60,50,40,0.3);
    box-shadow:
        0 4px 16px rgba(0,0,0,0.5),
        inset 0 1px 0 rgba(80,70,55,0.08);
    opacity: 0.6;
}
.jmcard--sealed:hover { transform: none; }
.jmcard-hatch {
    position: absolute;
    inset: 0;
    pointer-events: none;
    border-radius: 0.75rem;
    background-image: repeating-linear-gradient(
        -45deg,
        transparent, transparent 10px,
        rgba(30,24,16,0.35) 10px, rgba(30,24,16,0.35) 13px);
}

/* Top inner hairline */
.jmcard-top-hair {
    position: absolute;
    top: 0;
    right: 2rem;
    left: 2rem;
    height: 1px;
    pointer-events: none;
    background: linear-gradient(90deg, transparent, rgba(212,175,55,0.45), transparent);
}

/* Prime Realm banner */
.jmcard-prime-banner {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    padding: 0.375rem 0;
    font-family: 'Cinzel', serif;
    font-size: 0.8125rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.28em;
    background: linear-gradient(90deg,
        rgba(245,158,11,0) 0%,
        rgba(245,158,11,0.18) 20%,
        rgba(245,158,11,0.22) 50%,
        rgba(245,158,11,0.18) 80%,
        rgba(245,158,11,0) 100%);
    border-bottom: 1px solid rgba(245,158,11,0.25);
    color: #F59E0B;
    position: relative;
    z-index: 1;
}
/* Ribbon color variants — same silhouette as the gold Prime ribbon so all
   card headers are equal height and internal dividers align. */
.jmcard-banner--red {
    background: linear-gradient(90deg,
        rgba(239,68,68,0) 0%,
        rgba(239,68,68,0.16) 20%,
        rgba(239,68,68,0.20) 50%,
        rgba(239,68,68,0.16) 80%,
        rgba(239,68,68,0) 100%);
    border-bottom: 1px solid rgba(239,68,68,0.25);
    color: #F87171;
}
.jmcard-banner--red .jmcard-prime-dot {
    background: #F87171;
    box-shadow: 0 0 6px rgba(248,113,113,0.8);
}
.jmcard-banner--blue {
    background: linear-gradient(90deg,
        rgba(96,165,250,0) 0%,
        rgba(96,165,250,0.14) 20%,
        rgba(96,165,250,0.18) 50%,
        rgba(96,165,250,0.14) 80%,
        rgba(96,165,250,0) 100%);
    border-bottom: 1px solid rgba(96,165,250,0.25);
    color: #93C5FD;
}
.jmcard-banner--blue .jmcard-prime-dot {
    background: #93C5FD;
    box-shadow: 0 0 6px rgba(147,197,253,0.8);
}
.jmcard-prime-dot {
    width: 4px;
    height: 4px;
    border-radius: 9999px;
    background: #F59E0B;
    box-shadow: 0 0 6px rgba(245,158,11,0.8);
    display: inline-block;
}

/* Body */
.jmcard-body {
    position: relative;
    z-index: 1;
    padding: 1.5rem;
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    height: 100%;
}

/* Head row: live dot + name/subtitle/description */
.jmcard-head {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    margin-bottom: 0.75rem;
}
.jmcard-livedot {
    position: relative;
    margin-top: 0.4rem;
    width: 0.625rem;
    height: 0.625rem;
    flex-shrink: 0;
}
.jmcard-livedot-core,
.jmcard-livedot-ping {
    position: absolute;
    inset: 0;
    border-radius: 9999px;
}
.jmcard-livedot-core { background: rgb(82, 82, 91); }   /* offline = zinc-600 */
.jmcard-livedot.is-live .jmcard-livedot-core { background: #34D399; }
.jmcard-livedot-ping {
    background: #34D399;
    opacity: 0.6;
    animation: jmcardPing 2s cubic-bezier(0, 0, 0.2, 1) infinite;
}
@keyframes jmcardPing {
    75%, 100% { transform: scale(2.0); opacity: 0; }
}
.jmcard-head-text { flex: 1; min-width: 0; }
.jmcard-name {
    margin: 0 0 0.25rem;
    font-family: 'Inter', sans-serif;
    font-size: 0.8125rem;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    /*
     * Was `white-space: nowrap`, on the reasoning that the name always fits
     * beside the corner status pill. It was measured against the English
     * "Nexian Official" and it is not true of the translations: on a 375px
     * phone the text column shrinks to ~100px while the name renders 130px
     * (cs) or 171px (ru) wide, and nowrap with no `overflow` means it does
     * not clip - it paints straight over the pill. Measured overlap before
     * this change: en 11px, cs 17px, ru 63px.
     *
     * Wrapping keeps the WHOLE name readable, which is what the old comment
     * was protecting; ellipsising would not. There is still room for one
     * line at every width the desktop layouts use.
     */
    white-space: normal;
    overflow-wrap: break-word;
    color: rgba(212,175,55,0.85);
}
/* Phone: buy the name back enough room that the common translations still
   sit on ONE line, with wrapping above as the guarantee for the long ones. */
@media (max-width: 640px) {
    .jmcard-status-pill {
        font-size: 0.625rem;
        letter-spacing: 0.04em;
        padding-inline: 0.45rem;
    }
    .jmcard-name {
        font-size: 0.75rem;
        letter-spacing: 0.03em;
    }
}
.jmcard--sealed .jmcard-name { color: rgba(100,90,70,0.35); }
.jmcard-subtitle {
    margin: 0 0 0.375rem;
    font-family: 'Cinzel', serif;
    font-size: 1rem;
    font-weight: 700;
    line-height: 1.4;
    letter-spacing: 0.12em;
    color: #EDD98A;
    white-space: nowrap;   /* realm/round labels never wrap to two lines */
}
.jmcard--sealed .jmcard-subtitle { color: #3e3424; }
.jmcard-desc {
    margin: 0;
    font-size: 0.8125rem;
    line-height: 1.55;
    font-family: 'Inter', sans-serif;
    color: rgba(198, 180, 140, 0.85);
    transition: color 300ms ease;
}
.jmcard:hover .jmcard-desc { color: rgba(220,200,155,0.9); }
.jmcard--sealed .jmcard-desc { color: rgba(90,80,60,0.45); }

/* Day + Phase pills */
.jmcard-pills {
    display: flex;
    flex-wrap: wrap;
    gap: 0.375rem;
    margin-bottom: 1rem;
    min-height: 1.75rem;
}
.jmcard-pill {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    padding: 0.125rem 0.625rem;
    border-radius: 9999px;
    font-size: 0.8125rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    line-height: 1.4;
}
.jmcard-pill-day {
    background: rgba(212,175,55,0.08);
    border: 1px solid rgba(212,175,55,0.3);
    color: #D4AF37;
}
.jmcard-pill-phase {
    background: rgba(52,211,153,0.08);
    border: 1px solid rgba(52,211,153,0.3);
    color: #34D399;
}

/* Tribe roster — how many tribes this round offers. Violet is the hue none of
   the other pills use (gold day / emerald phase / red artifact / blue info) and
   is already the tribe colour in the picker below, so the roster reads as a
   property of the round rather than as another status. */
.jmcard-pill-tribes {
    background: rgba(192, 132, 252, 0.09);
    border: 1px solid rgba(192, 132, 252, 0.32);
    color: #D8B4FE;
    white-space: nowrap;
}
/* On the blue launching / red soon cards the violet at 9% sits too close to the
   card's own tint to read at a glance, so it steps up there. Same hue, more of
   it — not a different colour, which would make one realm look special. */
.jmcard--launching .jmcard-pill-tribes,
.jmcard--soon .jmcard-pill-tribes {
    background: rgba(192, 132, 252, 0.16);
    border-color: rgba(192, 132, 252, 0.5);
    color: #E9D5FF;
}
/* Artifact released — pulsing red */
.jmcard-pill-artifact {
    position: relative;
    overflow: hidden;
    background: linear-gradient(135deg, rgba(40,10,10,0.95) 0%, rgba(60,15,15,0.9) 100%);
    border: 1px solid rgba(248,113,113,0.5);
    color: #FCA5A5;
    animation: jmcardPillPulse 2.4s ease-in-out infinite;
}
.jmcard-pill-artifact-glow {
    position: absolute;
    inset: 0;
    pointer-events: none;
    border-radius: 9999px;
    background: radial-gradient(ellipse at 50% 120%, rgba(248,113,113,0.25) 0%, transparent 65%);
    animation: jmcardPillGlow 2.4s ease-in-out infinite;
}
.jmcard-pill-artifact .jmcard-pill-text { position: relative; z-index: 1; }
.jmcard-pill-artifact > svg { position: relative; z-index: 1; color: #F87171; }
@keyframes jmcardPillPulse {
    0%, 100% {
        box-shadow: 0 0 6px rgba(248,113,113,0.3), 0 0 14px rgba(200,60,60,0.15);
    }
    50% {
        box-shadow:
            0 0 14px rgba(248,113,113,0.7),
            0 0 28px rgba(200,60,60,0.4),
            inset 0 0 10px rgba(248,80,80,0.15);
    }
}
@keyframes jmcardPillGlow {
    0%, 100% { opacity: 0.4; }
    50%      { opacity: 1; }
}

/* Divider hairline */
/*
 * margin-top: auto here, NOT on .jmcard-actions.
 *
 * The buttons carried the auto margin, so they were the only thing that lined
 * up; everything above them floated on however many pills that card happened
 * to have. Measured across the three cards: Prime carries a second pill
 * ("Beginner Protection") and that pushed its stat trio 26px below the other
 * two, so the row of numbers ran downhill across the modal.
 *
 * Moving the auto up to the divider makes divider + stat block + buttons ONE
 * bottom-anchored group. Every middle block is already a fixed 5.5rem
 * (.jmcard-stats, .jmcard-devblock, .jmcard-comingsoon, .jmcard-soonblock), so
 * pinning the top of the group pins all of it - whatever state each card is in.
 * The pills keep the slack instead, which is where a variable-length list
 * belongs.
 */
.jmcard-divider {
    display: block;
    height: 1px;
    margin: auto 0 1.25rem;
    background: linear-gradient(90deg, transparent, rgba(212,175,55,0.2), transparent);
}

/* 3-stat boxes */
.jmcard-stats {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 0.5rem;
    margin-bottom: 1.25rem;
    height: 5.5rem;
}
.jmcard-stat {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 0.5rem;
    border-radius: 0.5rem;
    background: rgba(0,0,0,0.2);
}
.jmcard-stat-players { border: 1px solid rgba(212,175,55,0.20); box-shadow: 0 0 12px rgba(212,175,55,0.12); }
.jmcard-stat-online  { border: 1px solid rgba(52,211,153,0.20);  box-shadow: 0 0 12px rgba(52,211,153,0.12); }
.jmcard-stat-speed   { border: 1px solid rgba(248,113,113,0.20); box-shadow: 0 0 12px rgba(248,113,113,0.12); }
.jmcard-stat > svg { width: 0.75rem; height: 0.75rem; margin-bottom: 0.25rem; }
.jmcard-stat-players > svg { color: #D4AF37; }
.jmcard-stat-online  > svg { color: #34D399; }
.jmcard-stat-speed   > svg { color: #F87171; }
.jmcard-stat-val {
    font-family: 'Cinzel', serif;
    font-size: 1.25rem;
    font-weight: 700;
    line-height: 1;
    margin-bottom: 0.25rem;
    letter-spacing: -0.01em;
}
/* Glow on the digits cut 30% at the owner's request: alpha 0.6 -> 0.42 on all
   three, radius unchanged. The numbers now sit on a raised chip rather than a
   flat well, so they had less bloom to do to stand out. Only the alpha moved -
   scaling the radius as well would have changed the shape of the light, not
   just its strength. */
.jmcard-stat-players .jmcard-stat-val { color: #D4AF37; text-shadow: 0 0 14px rgba(212,175,55,0.42); }
.jmcard-stat-online  .jmcard-stat-val { color: #34D399; text-shadow: 0 0 14px rgba(52,211,153,0.42); }
.jmcard-stat-speed   .jmcard-stat-val { color: #F87171; text-shadow: 0 0 14px rgba(248,113,113,0.42); }
.jmcard-stat-lbl {
    font-family: 'Inter', sans-serif;
    /* Was 0.6875rem (11px), the smallest text anywhere in the modal, and at
       0.75 alpha the red one measured 4.45:1 - just under the 4.5 floor.
       0.75rem/12px against a 20px value still leaves the number clearly the
       hero of the tile; the ratio between them barely moves (20:11 -> 20:12).
       Alphas raised with it, most on the red because it was the one failing. */
    font-size: 0.75rem;
    font-weight: 500;
    letter-spacing: 0.1em;
    text-transform: uppercase;
}
.jmcard-stat-players .jmcard-stat-lbl { color: rgba(212,175,55,0.88); }
.jmcard-stat-online  .jmcard-stat-lbl { color: rgba(52,211,153,0.88); }
.jmcard-stat-speed   .jmcard-stat-lbl { color: rgba(250,140,140,0.95); }

/* Coming Soon orb (sealed cards) */
.jmcard-comingsoon {
    height: 5.5rem;
    margin-bottom: 1.25rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    opacity: 0.7;
    animation: jmcardCsoonPulse 3s ease-in-out infinite;
}
.jmcard-comingsoon-orb {
    width: 2.75rem;
    height: 2.75rem;
    border-radius: 9999px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: linear-gradient(145deg, rgba(55,44,30,0.9) 0%, rgba(28,22,14,0.95) 100%);
    border: 1px solid rgba(100,82,50,0.4);
    box-shadow: 0 4px 20px rgba(0,0,0,0.6), inset 0 1px 0 rgba(120,95,55,0.2);
    color: rgba(140,110,60,0.8);
}
.jmcard-comingsoon-text {
    font-family: 'Cinzel', serif;
    font-size: 0.8125rem;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.28em;
    color: rgb(146, 138, 132);
}
@keyframes jmcardCsoonPulse {
    0%, 100% { opacity: 0.45; }
    50%      { opacity: 0.7; }
}

/* Closed-registration pill (live but reg closed) */
.jmcard-closed-pill {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    margin: 0 0 0.5rem;
    padding: 0.25rem 0.625rem;
    border-radius: 9999px;
    background: rgba(180, 120, 60, 0.10);
    border: 1px solid rgba(180, 120, 60, 0.25);
    font-size: 0.8125rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: rgba(228, 180, 122, 0.9);
}

/* Action row */
.jmcard-actions {
    display: flex;
    gap: 0.625rem;
    /* The auto margin moved up to .jmcard-divider - see the note there. Two
       auto margins in one flex column SPLIT the free space between them, which
       would have left the stat trio floating in the middle again. */
    margin-top: 0;
}
.jmcard-btn {
    flex: 1;
    appearance: none;
    border: 1px solid transparent;
    padding: 0.75rem;
    border-radius: 0.5rem;
    text-align: center;
    font-family: 'Cinzel', serif;
    /* LOGIN and REGISTER are the two things this whole modal exists to offer.
       At 0.8125rem (13px) they were set smaller than the card's own "Nexian
       Official" eyebrow, inside a 46px-tall button with room to spare. 14px
       with the tracking eased slightly so the wider glyphs still fit the
       144px button in every language. */
    font-size: 0.875rem;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    cursor: pointer;
    transition: transform 200ms ease, filter 200ms ease;
}
.jmcard-btn:hover { transform: scale(1.05); filter: brightness(1.1); }
.jmcard-btn-login {
    background: linear-gradient(180deg, rgba(38,30,20,0.95) 0%, rgba(22,17,11,0.98) 100%);
    color: #D4AF37;
    border-color: rgba(212,175,55,0.35);
    box-shadow: 0 4px 16px rgba(0,0,0,0.35), inset 0 1px 0 rgba(212,175,55,0.1);
}
.jmcard-btn-register {
    background: linear-gradient(180deg, #D4AF37 0%, #A8891A 100%);
    color: #12100a;
    border-color: rgba(212,175,55,0.6);
    font-weight: 700;
    box-shadow: 0 4px 20px rgba(212,175,55,0.45), inset 0 1px 0 rgba(255,255,220,0.25);
}
.jmcard-btn-sealed {
    cursor: not-allowed;
    user-select: none;
    background: linear-gradient(180deg, rgba(30,24,16,0.7) 0%, rgba(18,14,9,0.8) 100%);
    color: rgba(100,82,50,0.5);
    border-color: rgba(70,55,35,0.2);
    font-weight: 900;
    letter-spacing: 0.25em;
}
.jmcard-btn-sealed:hover { transform: none; filter: none; }

/* Launching → "Server Details" gold link. Same V0 silhouette as Register
   but rendered as an <a> so it can navigate cross-page to /serverdetails. */
.jmcard-btn-details {
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: linear-gradient(180deg, #D4AF37 0%, #A8891A 100%);
    color: #12100a;
    border-color: rgba(212,175,55,0.6);
    font-weight: 700;
    box-shadow: 0 4px 20px rgba(212,175,55,0.45), inset 0 1px 0 rgba(255,255,220,0.25);
}

/* Disabled per-channel button (registration_open=false or login_open=false).
 * Stays visible — full V0 button silhouette — but desaturated, no hover lift,
 * cursor: not-allowed, and a custom tooltip via data-tooltip + ::after that
 * appears on hover. */
.jmcard-btn.is-disabled,
.jmcard-btn[disabled] {
    cursor: not-allowed;
    /* Skip the inherited filter transition on the disabled state so the
       desaturate snaps in instead of animating from the enabled gold — keeps
       the visual unambiguous and avoids any backgrounded-tab throttle quirks. */
    transition: transform 200ms ease;
    filter: grayscale(0.85) brightness(0.55);
    opacity: 0.6;
    position: relative;
}
.jmcard-btn.is-disabled:hover,
.jmcard-btn[disabled]:hover {
    transform: none;
    filter: grayscale(0.85) brightness(0.55);
}
.jmcard-btn[data-tooltip] {
    position: relative;
}
.jmcard-btn[data-tooltip]::after {
    content: attr(data-tooltip);
    position: absolute;
    bottom: calc(100% + 0.5rem);
    right: 50%;
    transform: translateX(50%) translateY(4px);
    padding: 0.4rem 0.625rem;
    border-radius: 0.375rem;
    background: rgba(8, 5, 3, 0.96);
    border: 1px solid rgba(212, 175, 55, 0.4);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.55), 0 0 12px rgba(212, 175, 55, 0.18);
    color: #E8D9B0;
    font-family: 'Inter', sans-serif;
    font-size: 0.8125rem;
    font-weight: 500;
    letter-spacing: 0.04em;
    text-transform: none;
    line-height: 1.4;
    white-space: nowrap;
    pointer-events: none;
    opacity: 0;
    transition: opacity 200ms ease, transform 200ms ease;
    z-index: 20;
}
.jmcard-btn[data-tooltip]::before {
    /* Tiny pointer triangle */
    content: '';
    position: absolute;
    bottom: 100%;
    right: 50%;
    transform: translateX(50%) translateY(4px);
    border: 5px solid transparent;
    border-top-color: rgba(212, 175, 55, 0.4);
    pointer-events: none;
    opacity: 0;
    transition: opacity 200ms ease, transform 200ms ease;
    z-index: 20;
}
.jmcard-btn[data-tooltip]:hover::after,
.jmcard-btn[data-tooltip]:hover::before {
    opacity: 1;
    transform: translateX(50%) translateY(0);
}

/* ─── View 2: register grid (info rail + form) ───────────────────────────── */

.journey-register-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1.25rem;
}
@media (min-width: 1024px) {
    .journey-register-grid { grid-template-columns: 280px 1fr; }
}
.journey-info-rail > div { height: 100%; }

/* ─── View 3: login grid (server slot + form spans the rest) ─────────────── */

.journey-login-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1rem;
    align-items: stretch;
}
@media (min-width: 768px) {
    .journey-login-grid { grid-template-columns: repeat(3, 1fr); }
    .journey-login-slot { grid-column: span 1; }
    .jform-login        { grid-column: span 2; }
}

/* ─── ServerInfoCard (jic) — left rail of register view ──────────────────── */

.jic {
    position: relative;
    overflow: hidden;
    border-radius: 0.75rem;
    /* Stretch to fill the column height set by the grid row. Coupled with
     * the flex column below, the inner sections distribute spare height so
     * the card never has a dead zone at the bottom regardless of how tall
     * the form on the right becomes. */
    height: 100%;
    min-height: 100%;
    display: flex;
    flex-direction: column;
    background: linear-gradient(165deg, rgba(38,28,18,0.72) 0%, rgba(18,14,9,0.80) 100%);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border: 1px solid rgba(212,175,55,0.25);
    box-shadow: 0 8px 32px rgba(0,0,0,0.4);
}
.jic.jic--prime {
    border-color: rgba(245,158,11,0.55);
    box-shadow: 0 0 20px rgba(245,158,11,0.12), 0 8px 32px rgba(0,0,0,0.45);
}
.jic-watermark {
    position: absolute;
    inset: 0;
    z-index: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0.05;
    pointer-events: none;
}
.jic-watermark img { width: 15rem; height: 15rem; object-fit: contain; }
.jic-prime-banner {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    padding: 0.375rem 0;
    font-family: 'Cinzel', serif;
    font-size: 0.8125rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.35em;
    background: linear-gradient(90deg,
        rgba(245,158,11,0) 0%,
        rgba(245,158,11,0.18) 20%,
        rgba(245,158,11,0.22) 50%,
        rgba(245,158,11,0.18) 80%,
        rgba(245,158,11,0) 100%);
    border-bottom: 1px solid rgba(245,158,11,0.25);
    color: #F59E0B;
    position: relative;
    z-index: 1;
}
.jic-prime-dot {
    width: 4px;
    height: 4px;
    border-radius: 9999px;
    background: #F59E0B;
    box-shadow: 0 0 6px rgba(245,158,11,0.8);
    display: inline-block;
}
.jic-body {
    position: relative;
    z-index: 1;
    padding: 1.25rem;
    /* Flex column so Game Speeds + Server Timeline can grow into spare
     * vertical space when the form on the right is taller than the rail's
     * natural content height. */
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
}
.jic-name-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0.5rem;
}
.jic-live-dot {
    position: relative;
    width: 0.625rem;
    height: 0.625rem;
}
.jic-live-dot-core,
.jic-live-dot-ping {
    position: absolute;
    inset: 0;
    border-radius: 9999px;
    background: #34D399;
}
.jic-live-dot-ping {
    opacity: 0.6;
    animation: jicPing 2s cubic-bezier(0, 0, 0.2, 1) infinite;
}
@keyframes jicPing {
    75%, 100% { transform: scale(2.0); opacity: 0; }
}
.jic-name {
    font-family: 'Inter', sans-serif;
    font-size: 0.8125rem;
    font-weight: 500;
    letter-spacing: 0.3em;
    text-transform: uppercase;
    color: rgba(212,175,55,0.5);
}
.jic-subtitle {
    margin: 0 0 0.5rem;
    font-family: 'Cinzel', serif;
    font-size: 1.125rem;
    font-weight: 700;
    line-height: 1.35;
    letter-spacing: 0.12em;
    color: #EDD98A;
}
.jic-desc {
    margin: 0 0 1rem;
    font-size: 0.8125rem;
    line-height: 1.5;
    color: rgba(190,170,130,0.65);
}
.jic-pills { display: flex; flex-wrap: wrap; gap: 0.375rem; margin-bottom: 1rem; }
.jic-pill {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    padding: 0.125rem 0.625rem;
    border-radius: 9999px;
    font-size: 0.8125rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.15em;
}
.jic-pill-day {
    background: rgba(212,175,55,0.08);
    border: 1px solid rgba(212,175,55,0.3);
    color: #D4AF37;
}
.jic-pill-phase-green {
    background: rgba(52,211,153,0.08);
    border: 1px solid rgba(52,211,153,0.3);
    color: #34D399;
}
.jic-pill-phase-red {
    background: rgba(248,113,113,0.1);
    border: 1px solid rgba(248,113,113,0.5);
    color: #FCA5A5;
}
.jic-hr {
    height: 1px;
    margin-bottom: 1rem;
    background: linear-gradient(90deg, transparent, rgba(212,175,55,0.2), transparent);
}
.jic-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.5rem; }
.jic-stat {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 0.5rem;
    border-radius: 0.5rem;
}
.jic-stat-players { background: rgba(212,175,55,0.07); border: 1px solid rgba(212,175,55,0.25); color: #D4AF37; }
.jic-stat-online  { background: rgba(52,211,153,0.07);  border: 1px solid rgba(52,211,153,0.25);  color: #34D399; }
.jic-stat-speed   { background: rgba(248,113,113,0.07); border: 1px solid rgba(248,113,113,0.30); color: #FCA5A5; }
.jic-stat-val {
    font-family: 'Cinzel', serif;
    font-size: 1.125rem;
    font-weight: 700;
    line-height: 1;
    margin-top: 0.25rem;
    color: inherit;
}
.jic-stat-lbl {
    font-size: 0.8125rem;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    margin-top: 0.125rem;
    opacity: 0.7;
}
.jic-meta-tiles {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.5rem;
    margin-top: 0.75rem;
}
.jic-meta-tile {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 0.625rem 0.5rem;
    border-radius: 0.5rem;
    background: rgba(0,0,0,0.2);
    border: 1px solid rgba(212,175,55,0.15);
}
.jic-meta-val {
    font-family: 'Cinzel', serif;
    font-size: 0.8125rem;
    font-weight: 700;
    color: #D4AF37;
}
.jic-meta-lbl {
    font-size: 0.8125rem;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    margin-top: 0.25rem;
    color: rgba(212,175,55,0.5);
}
.jic-section-divider {
    height: 1px;
    margin: 1rem -1.25rem;
    background: linear-gradient(90deg, transparent, rgba(212,175,55,0.2), transparent);
}
.jic-section-head {
    margin: 0 -1.25rem 0.875rem;
    padding: 0.6875rem 1.25rem;
    font-family: 'Cinzel', serif;
    font-size: 0.8125rem;
    font-weight: 700;
    letter-spacing: 0.3em;
    text-transform: uppercase;
    color: #C9A84C;
    text-align: center;
    background: linear-gradient(180deg, rgba(6,4,2,0.6) 0%, rgba(14,10,5,0.4) 100%);
    border-top: 1px solid rgba(212,175,55,0.18);
    border-bottom: 1px solid rgba(212,175,55,0.18);
}
/* Game Speeds — grows to absorb spare vertical space, distributing rows
 * with even spacing rather than letting empty space pile up at the bottom. */
.jic-speeds {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    gap: 0.375rem;
    flex: 1 1 auto;
    min-height: 0;
}
.jic-speed-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.5rem 0.625rem;
    border-radius: 4px;
    flex: 0 0 auto;
}
.jic-speed-row.striped { background: rgba(0,0,0,0.15); }
.jic-speed-label { font-size: 0.8125rem; color: rgba(210, 190, 145, 0.88); }
.jic-speed-value { font-size: 0.875rem; font-weight: 700; color: #7DD3CC; }
/* Server Timeline — last section in the rail. Grows ~1.4× as eagerly as
 * Game Speeds so when the form is much taller than the rail's natural
 * content, the timeline absorbs more of the spare height (it has more
 * visual weight per event than a speed row). */
.jic-timeline {
    padding-right: 0.75rem;
    border-right: 1px solid rgba(212,175,55,0.2);
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    flex: 1.4 1 auto;
    min-height: 0;
    gap: 0.25rem;
}
.jic-tl-item {
    position: relative;
    padding: 0.5rem 0.625rem;
    margin-right: -0.5rem;
    border-radius: 4px 0 0 4px;
    flex: 0 0 auto;
}
.jic-tl-item.is-current {
    background: linear-gradient(90deg, rgba(212,175,55,0.12) 0%, rgba(212,175,55,0.06) 70%, transparent 100%);
    border-right: 2px solid rgba(212,175,55,0.6);
    margin-right: -0.625rem;
    padding-right: 0.625rem;
}
.jic-tl-dot {
    position: absolute;
    top: 0.5rem;
    right: calc(-0.75rem - 1.5px);
    width: 0.375rem;
    height: 0.375rem;
    border-radius: 9999px;
    background: #D4AF37;
    box-shadow: 0 0 4px rgba(212,175,55,0.4);
}
.jic-tl-item.is-current .jic-tl-dot {
    right: calc(-0.75rem - 2px);
    background: #F4E287;
    box-shadow: 0 0 8px rgba(244,226,135,0.9), 0 0 16px rgba(212,175,55,0.5);
}
.jic-tl-now {
    position: absolute;
    left: 0.5rem;
    top: 0.375rem;
    font-size: 0.8125rem;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    font-weight: 700;
    color: rgba(212,175,55,0.6);
}
.jic-tl-date {
    margin: 0;
    font-family: 'Cinzel', serif;
    font-size: 0.8125rem;
    font-weight: 600;
    line-height: 1.15;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: #C9A84C;
}
.jic-tl-item.is-current .jic-tl-date  { color: #F4E287; }
.jic-tl-item .jic-tl-event { margin: 0.125rem 0 0; font-size: 0.8125rem; color: rgba(208,193,153,0.88); }
.jic-tl-item.is-current .jic-tl-event { color: rgba(230,210,160,0.9); }

/* ─── Form panels (Account Details / Choose Tribe / Starting Position) ──── */

.journey-form {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}
.jform-panel {
    border: 1px solid rgba(212,175,55,0.15);
    border-radius: 0.75rem;
    padding: 1rem;
    background: linear-gradient(165deg, rgba(30,22,14,0.6) 0%, rgba(18,13,8,0.7) 100%);
    margin: 0;
}
.jform-panel-head {
    display: block;
    margin: -1rem -1rem 0.75rem;
    padding: 0.625rem 1rem;
    text-align: center;
    font-family: 'Cinzel', serif;
    font-size: 0.875rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.28em;
    color: #E5C45E;
    background: linear-gradient(180deg, rgba(6,4,2,0.75) 0%, rgba(14,10,5,0.55) 100%);
    border-radius: 0.75rem 0.75rem 0 0;
    border-bottom: 1px solid rgba(212,175,55,0.12);
    position: relative;
}
.jform-panel-head::after {
    content: '';
    position: absolute;
    right: 2rem;
    left: 2rem;
    top: 0;
    height: 1px;
    background: linear-gradient(90deg, transparent, rgba(212,175,55,0.2), transparent);
}
.jform-fields { display: flex; flex-direction: column; gap: 0.625rem; }
.jfield { display: flex; flex-direction: column; gap: 0.375rem; }
.jfield-label {
    font-size: 0.8125rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.15em;
    color: rgba(232, 200, 100, 0.92);
}
.jfield-wrap { position: relative; }
.jfield-eye {
    position: absolute;
    left: 0.75rem;
    top: 50%;
    transform: translateY(-50%);
    background: none;
    border: none;
    color: rgba(232, 200, 100, 0.75);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
}
.jfield-eye:hover { color: rgba(244, 226, 135, 0.95); }

/* Engraved input (matches V0's `.engraved-input` exactly) */
.engraved-input {
    width: 100%;
    padding: 0.6875rem 1rem;
    border-radius: 0.5rem;
    font-size: 0.9375rem;
    outline: none;
    color: #F5E5A8;
    background: rgba(4, 3, 2, 0.55);
    border: 1px solid rgba(110, 90, 50, 0.40);
    border-bottom: 2px solid rgba(110, 90, 50, 0.45);
    box-shadow:
        inset 0 3px 8px rgba(0, 0, 0, 0.55),
        inset 0 1px 3px rgba(0, 0, 0, 0.4);
    transition: border-bottom-color 0.2s ease, box-shadow 0.2s ease;
    font-family: inherit;
}
.engraved-input.has-eye { padding-left: 2.5rem; }
.engraved-input::placeholder { color: rgba(180, 150, 90, 0.70); }

/* Confirm-Password mismatch state — red bottom border + small message below.
 * Falls back to default border colour the moment the values match again.
 * Skips the inherited transition so the colour swap is instant and never
 * stuck mid-tween on a backgrounded tab. */
.engraved-input.has-error {
    transition: none;
    border-bottom-color: rgba(248, 113, 113, 0.75) !important;
    box-shadow:
        inset 0 3px 8px rgba(0, 0, 0, 0.55),
        inset 0 1px 3px rgba(0, 0, 0, 0.4),
        0 2px 12px rgba(248, 113, 113, 0.25) !important;
}
.jfield-error {
    margin-top: 0.25rem;
    font-size: 0.8125rem;
    font-weight: 500;
    color: #FCA5A5;
    text-shadow: 0 0 8px rgba(248, 113, 113, 0.35);
    line-height: 1.3;
}

/* hCaptcha widget wrapper — visible Classic challenge, centred in form. */
.jform-hcaptcha-wrap {
    display: flex;
    justify-content: center;
    margin: 0.5rem 0;
    min-height: 80px;
}
.jform-hcaptcha-wrap iframe { border-radius: 4px; }

/* Captcha-blocked watchdog notice (shown when the hCaptcha SDK never
   rendered, typically an ad-blocker). Amber: actionable warning, not a
   submit error. The trailing <a> is the world's direct register URL. */
.jform-captcha-notice {
    margin: 0.25rem 0;
    padding: 0.625rem 0.875rem;
    border-radius: 0.5rem;
    font-size: 0.8125rem;
    line-height: 1.5;
    text-align: center;
    color: #FDE68A;
    background: rgba(245, 158, 11, 0.10);
    border: 1px solid rgba(245, 158, 11, 0.40);
}
.jform-captcha-notice a {
    color: #FCD34D;
    font-weight: 600;
    text-decoration: underline;
    text-underline-offset: 2px;
    word-break: break-all;
}
.jform-captcha-notice a:hover { color: #FDE68A; }

/* Inline status row driven by the AJAX submit pipeline. Three kinds:
 *   .is-info     blue/neutral — verifying / securing / creating
 *   .is-error    red — server-side reject or network failure
 *   .is-success  gold — account created */
.jform-status {
    margin: 0.25rem 0;
    padding: 0.625rem 0.875rem;
    border-radius: 0.5rem;
    font-size: 0.8125rem;
    line-height: 1.4;
    text-align: center;
}
.jform-status.is-info {
    color: #BFDBFE;
    background: rgba(59, 130, 246, 0.10);
    border: 1px solid rgba(96, 165, 250, 0.30);
}
.jform-status.is-error {
    color: #FECACA;
    background: rgba(239, 68, 68, 0.12);
    border: 1px solid rgba(248, 113, 113, 0.45);
}
.jform-status.is-success {
    color: #FDE68A;
    background: rgba(212, 175, 55, 0.12);
    border: 1px solid rgba(212, 175, 55, 0.45);
    font-weight: 600;
}
.engraved-input:focus {
    border-bottom-color: #D4AF37;
    box-shadow:
        inset 0 3px 8px rgba(0, 0, 0, 0.55),
        inset 0 1px 3px rgba(0, 0, 0, 0.4),
        0 2px 12px rgba(212, 175, 55, 0.25);
}

/* ─── Tribe cards ─────────────────────────────────────────────────────────── */

.jform-note {
    margin: 0 0 0.5rem;
    font-size: 0.8125rem;
    text-align: center;
    line-height: 1.5;
    color: rgba(220, 195, 140, 0.92);
}
.jform-note a {
    color: #C9A84C;
    text-decoration: underline;
    text-underline-offset: 2px;
}
.tribe-grid {
    display: grid;
    /*
     * Not repeat(3, 1fr). The roster is three today and five the day Egyptians
     * and Huns go live, and a hardcoded 3 would have put the last two on a
     * second row, left-aligned under the first three - a broken-looking shelf.
     *
     * auto-fit + minmax lets the count decide: at the ~744px this panel gets on
     * a desktop that is 3 wide for three tribes and 5 wide for five, one row
     * either way, and it steps down to 2 on a phone without another rule.
     */
    grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr));
    gap: 0.5rem;
}
.tribe-card {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 0.625rem;
    border-radius: 0.75rem;
    cursor: pointer;
    background: rgba(0,0,0,0.2);
    border: 1px solid rgba(140, 115, 70, 0.30);
    color: #BFA470;
    transition: opacity 250ms ease, transform 250ms ease, background 250ms ease,
                border-color 250ms ease, box-shadow 250ms ease, color 250ms ease;
}
.tribe-card:hover { transform: scale(1.03); filter: brightness(1.07); }
.tribe-card.is-dim { opacity: 0.38; }
.tribe-card.is-selected {
    transform: scale(1.03);
    background: linear-gradient(165deg, color-mix(in srgb, var(--tribe-color) 9%, transparent) 0%,
                                         color-mix(in srgb, var(--tribe-color) 4%, transparent) 100%);
    border: 1.5px solid color-mix(in srgb, var(--tribe-color) 50%, transparent);
    color: var(--tribe-color);
    box-shadow:
        0 0 24px color-mix(in srgb, var(--tribe-color) 30%, transparent),
        0 0 8px  color-mix(in srgb, var(--tribe-color) 30%, transparent),
        inset 0 1px 0 color-mix(in srgb, var(--tribe-color) 12%, transparent);
}
.tribe-icon {
    display: inline-flex;
    margin-bottom: 0.25rem;
    color: inherit;
    filter: drop-shadow(0 2px 4px rgba(0,0,0,0.6));
}
.tribe-card.is-selected .tribe-icon {
    filter: drop-shadow(0 0 8px var(--tribe-color)) drop-shadow(0 0 16px var(--tribe-color));
}
.tribe-name {
    font-family: 'Cinzel', serif;
    font-size: 0.8125rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    color: inherit;
}
.tribe-desc {
    margin-top: 0.25rem;
    font-size: 0.8125rem;
    text-align: center;
    line-height: 1.35;
    color: rgba(205, 185, 145, 0.82);
}
.tribe-card.is-selected .tribe-desc { color: rgba(235, 220, 175, 0.96); }
.tribe-check {
    position: absolute;
    top: 0.375rem;
    left: 0.375rem;
    width: 0.75rem;
    height: 0.75rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 9999px;
    background: var(--tribe-color);
    box-shadow: 0 0 8px var(--tribe-color);
}

/* ─── Starting Position ──────────────────────────────────────────────────── */

.pos-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.375rem;
    margin-bottom: 0.375rem;
}
.pos-card {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 0.5rem;
    border-radius: 0.5rem;
    background: rgba(0,0,0,0.25);
    border: 1px solid rgba(100,80,50,0.22);
    box-shadow: inset 0 2px 6px rgba(0,0,0,0.35);
    cursor: pointer;
    font-family: 'Cinzel', serif;
    transition: background 200ms ease, border 200ms ease, box-shadow 200ms ease, transform 200ms ease;
}
.pos-card:hover { transform: scale(1.03); }
.pos-card.is-selected {
    background: linear-gradient(165deg, rgba(212,175,55,0.14) 0%, rgba(212,175,55,0.06) 100%);
    border: 1.5px solid rgba(212,175,55,0.55);
    box-shadow: 0 0 16px rgba(212,175,55,0.18), inset 0 1px 0 rgba(212,175,55,0.12);
}
.pos-corner {
    font-size: 1rem;
    font-weight: 700;
    line-height: 1;
    margin-bottom: 0.25rem;
    color: #BFA470;
}
.pos-card.is-selected .pos-corner { color: #F8E89A; }
.pos-label {
    font-size: 0.8125rem;
    line-height: 1.25;
    text-transform: uppercase;
    letter-spacing: 0.18em;
    color: rgba(200, 175, 130, 0.78);
    text-align: center;
}
.pos-card.is-selected .pos-label { color: rgba(244, 226, 135, 0.92); }
.pos-coords {
    margin-top: 0.25rem;
    font-size: 0.8125rem;
    font-family: ui-monospace, monospace;
    color: rgba(180, 160, 120, 0.68);
}
.pos-card.is-selected .pos-coords { color: rgba(232, 200, 100, 0.78); }
.pos-dot {
    position: absolute;
    top: 0.5rem;
    left: 0.5rem;
    width: 0.375rem;
    height: 0.375rem;
    border-radius: 9999px;
    background: #D4AF37;
    box-shadow: 0 0 6px rgba(212,175,55,0.8);
}

/* "Recommended" ribbon on the least-populated quadrant. Sits in the top-right
 * corner of the pos-card. Hidden the moment the user clicks the card. */
.pos-rec-ribbon {
    position: absolute;
    top: -0.4rem;
    left: -0.4rem;
    display: inline-flex;
    align-items: center;
    gap: 0.2rem;
    padding: 0.15rem 0.4rem 0.15rem 0.5rem;
    border-radius: 9999px;
    background: linear-gradient(135deg, #34D399 0%, #059669 100%);
    color: #06281a;
    font-family: 'Cinzel', serif;
    font-size: 0.8125rem;
    font-weight: 800;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    line-height: 1;
    box-shadow:
        0 2px 8px rgba(5, 150, 105, 0.45),
        0 0 0 1px rgba(255, 255, 255, 0.12),
        0 0 12px rgba(52, 211, 153, 0.35);
    animation: posRecPulse 2.8s ease-in-out infinite;
    pointer-events: none;
    z-index: 2;
}
.pos-rec-star {
    color: #FDE68A;
    text-shadow: 0 0 6px rgba(253, 230, 138, 0.7);
    font-size: 0.8125rem;
    line-height: 1;
}
@keyframes posRecPulse {
    0%, 100% {
        box-shadow: 0 2px 8px rgba(5, 150, 105, 0.45),
                    0 0 0 1px rgba(255, 255, 255, 0.12),
                    0 0 12px rgba(52, 211, 153, 0.35);
    }
    50% {
        box-shadow: 0 2px 10px rgba(5, 150, 105, 0.65),
                    0 0 0 1px rgba(255, 255, 255, 0.18),
                    0 0 22px rgba(52, 211, 153, 0.55);
    }
}

/* Recommended (but not yet selected) → subtle green tint on the card itself
 * so the recommendation reads even before the user notices the ribbon. */
.pos-card.is-recommended {
    background: linear-gradient(165deg, rgba(52, 211, 153, 0.08) 0%, rgba(52, 211, 153, 0.03) 100%);
    border-color: rgba(52, 211, 153, 0.42);
    box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.30),
                0 0 10px rgba(52, 211, 153, 0.10);
}
.pos-card.is-recommended .pos-corner { color: #D1FAE5; }
.pos-card.is-recommended .pos-label  { color: rgba(167, 243, 208, 0.85); }
.pos-card.is-recommended .pos-coords { color: rgba(110, 231, 183, 0.72); }
/* When the user clicks the recommended one: `is-selected` wins for the
 * gold card treatment AND the ribbon stays visible (user-requested). The
 * tiny .pos-dot also stays visible — moved to the TOP-LEFT corner so the
 * ribbon at the top-right doesn't sit on top of it. */
.pos-card.is-selected.is-recommended .pos-dot {
    top: 0.5rem;
    right: 0.5rem;
    left: auto;
}
.pos-card.is-selected.is-recommended .pos-corner { color: #FDE68A; }
.pos-card.is-selected.is-recommended .pos-label  { color: rgba(244, 226, 135, 0.95); }
.pos-card.is-selected.is-recommended .pos-coords { color: rgba(232, 200, 100, 0.78); }
.pos-random {
    appearance: none;
    width: 100%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.625rem;
    padding: 0.625rem;
    border-radius: 0.5rem;
    background: linear-gradient(180deg, rgba(70, 70, 70, 0.35) 0%, rgba(45, 45, 45, 0.40) 100%);
    border: 1px solid rgba(140, 140, 140, 0.25);
    box-shadow: inset 0 1px 4px rgba(0,0,0,0.2);
    cursor: pointer;
    font-family: 'Cinzel', serif;
    font-size: 0.875rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.22em;
    color: #B5B5B5;
    transition: all 200ms ease;
}
.pos-random.is-selected {
    background: linear-gradient(180deg, rgba(100, 100, 100, 0.55) 0%, rgba(65, 65, 65, 0.62) 50%, rgba(45, 45, 45, 0.66) 100%);
    color: #ECECEC;
    border-color: rgba(170, 170, 170, 0.30);
}
.pos-random:hover { transform: scale(1.02); }

/* ─── Rules check ─────────────────────────────────────────────────────────── */

/* Consent box — deliberately prominent (players were missing it): gold
   border + tinted background + larger text and checkbox. */
.rules-check {
    display: flex;
    align-items: center;
    gap: 0.875rem;
    padding: 0.875rem 1rem;
    border-radius: 0.5rem;
    background: linear-gradient(180deg, rgba(212,175,55,0.10) 0%, rgba(212,175,55,0.04) 100%);
    border: 1px solid rgba(212,175,55,0.45);
    box-shadow: inset 0 1px 0 rgba(212,175,55,0.10);
    cursor: pointer;
    position: relative;
}
/* Native checkbox: visually hidden but focusable — keyboard (Tab + Space)
   and screen readers work; the styled .rules-box mirrors its state. */
.rules-native {
    position: absolute;
    opacity: 0;
    width: 1px;
    height: 1px;
    margin: 0;
    pointer-events: none;
}
.rules-native:focus-visible + .rules-box {
    outline: 2px solid rgba(212,175,55,0.85);
    outline-offset: 2px;
}
.rules-box {
    flex-shrink: 0;
    width: 1.375rem;
    height: 1.375rem;
    border-radius: 0.3rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: rgba(0,0,0,0.45);
    border: 1.5px solid rgba(212,175,55,0.55);
    box-shadow: inset 0 2px 4px rgba(0,0,0,0.4);
    transition: all 200ms ease;
}
.rules-box.is-checked {
    background: linear-gradient(180deg, #D4AF37 0%, #A8891A 100%);
    border-color: rgba(212,175,55,0.9);
    box-shadow: 0 0 10px rgba(212,175,55,0.4);
}
.rules-text {
    font-size: 0.9375rem;
    line-height: 1.5;
    color: #EFE3BD;
}
.rules-text a { color: #F4D061; text-decoration: underline; text-underline-offset: 2px; font-weight: 600; }
.rules-text a:hover { color: #FCE694; }

/* ─── Submit button ──────────────────────────────────────────────────────── */

.journey-submit {
    appearance: none;
    width: 100%;
    padding: 0.875rem;
    margin-top: 0.5rem;
    border-radius: 0.5rem;
    background: linear-gradient(180deg, #D4AF37 0%, #A8891A 100%);
    color: #12100a;
    border: none;
    box-shadow: 0 6px 30px rgba(212,175,55,0.4), inset 0 1px 0 rgba(255,255,220,0.3);
    font-family: 'Cinzel', serif;
    font-size: 1rem;
    font-weight: 900;
    text-transform: uppercase;
    letter-spacing: 0.25em;
    cursor: pointer;
    transition: transform 250ms ease, filter 250ms ease;
}
.journey-submit:hover:not(:disabled) { transform: scale(1.015); filter: brightness(1.1); }
.journey-submit:disabled {
    opacity: 0.4;
    cursor: not-allowed;
    box-shadow: none;
}

/* ─── Login sub-header ───────────────────────────────────────────────────── */

.jform-login {
    background: linear-gradient(135deg, rgba(25,18,10,0.95) 0%, rgba(15,10,5,0.98) 100%);
    border: 1px solid rgba(212,175,55,0.18);
    border-radius: 1rem;
    padding: 1.25rem;
    box-shadow: 0 8px 40px rgba(0,0,0,0.5), inset 0 1px 0 rgba(212,175,55,0.1);
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}
/* .jform-subhead and its two .line rules were removed on 2026-08-22. The login
   heading was the only element that ever carried the class, and it is a
   .jform-panel-head now like every other section heading in this modal. */
.forgot-pw { text-align: left; margin-top: -0.25rem; }
.forgot-pw a {
    font-family: 'Cinzel', serif;
    font-size: 0.8125rem;
    text-decoration: underline;
    text-underline-offset: 2px;
    color: rgba(215, 190, 130, 0.85);
}
.forgot-pw a:hover { color: rgba(244, 226, 135, 1); }

/* ─── One-account warning row ────────────────────────────────────────────── */

.one-account-warn {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0.625rem 0;
    overflow: hidden;
    margin-top: 0.25rem;
}
.oac-fade {
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: linear-gradient(90deg,
        transparent 0%,
        rgba(239,68,68,0.08) 25%,
        rgba(239,68,68,0.10) 50%,
        rgba(239,68,68,0.08) 75%,
        transparent 100%);
}
.oac-line {
    position: absolute;
    right: 0;
    left: 0;
    height: 1px;
    pointer-events: none;
    background: linear-gradient(90deg,
        transparent 0%,
        rgba(239,68,68,0.4) 30%,
        rgba(239,68,68,0.4) 70%,
        transparent 100%);
}
.oac-line.top    { top: 0; }
.oac-line.bottom { bottom: 0; }
.one-account-warn p {
    position: relative;
    margin: 0;
    font-family: 'Cinzel', serif;
    font-size: 0.8125rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.18em;
    text-shadow: 0 0 10px rgba(239,68,68,0.6), 0 0 24px rgba(239,68,68,0.3);
    color: #FCA5A5;
}


/* ============================================================================
   2026-08-22 - PANEL LANGUAGE ALIGNED WITH THE REFURBISHED PANELS
   ----------------------------------------------------------------------------
   The Play Now flow (and the login and register panels inside it) was the last
   surface still speaking the old vocabulary: four L-shaped corner brackets, a
   flat gold hairline, and a close button that only scaled up. The manual and
   server-details panels were rebuilt on a different language in August and this
   brings the journey across, so the site reads as one product:

     RIM    - a 1px gradient hairline that follows the radius, brightest top
              left, fading to nothing bottom right. Depth from the edge itself
              rather than ornament bolted 1rem inside it.
     BLOOM  - a soft wash of light spilling down from the top edge, tinted by
              --panel-accent.
     CLOSE  - a lit disc that rotates 90 degrees, not a square that scales.
     NO CORNER BRACKETS - the owner had these removed site-wide; the journey
              modal was simply missed.

   Appended rather than edited in place so every rule here demonstrably wins,
   and so the block can be lifted out in one piece if it is ever unwanted.
   ============================================================================ */

.journey-panel,
.jform-panel {
    /* MUST be declared on both. color-mix() with an unset custom property is
       invalid at computed-value time and silently discards the WHOLE
       declaration it appears in - which is how this rim once rendered as
       nothing on one panel while looking perfect on the other. */
    --panel-accent: #D4AF37;
    /* Creates a stacking context so the bloom below can sit at z-index -1:
       above the panel's own background, below everything the reader touches.
       Without it the bloom paints over the content, or disappears behind the
       page. .journey-panel already isolates via backdrop-filter; stated anyway
       so this rule does not depend on that staying true. */
    isolation: isolate;
}
.jform-panel { position: relative; }

/* --- Corner brackets retired --------------------------------------------- */
/* Markup removed from components/journey-modal.phtml in the same pass; this
   stays as a guard in case a copy of the span survives somewhere, so the motif
   cannot quietly reappear. */
.journey-corner { display: none !important; }

/* --- RIM ------------------------------------------------------------------ */
/* Gradient border via the two-layer mask trick: one element carrying a
   gradient on a 1px edge that follows the border-radius. */
.journey-panel::after,
.jform-panel::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    padding: 1px;
    pointer-events: none;
    z-index: 3;
    background: linear-gradient(145deg,
        rgba(255, 244, 214, 0.55) 0%,
        color-mix(in srgb, var(--panel-accent) 30%, transparent) 18%,
        rgba(212, 175, 55, 0.10) 46%,
        rgba(255, 255, 255, 0.03) 72%,
        rgba(255, 255, 255, 0.06) 100%);
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
            mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
            mask-composite: exclude;
    transition: background 0.45s ease;
}
/* The form panels sit inside the modal, so their rim is quieter - two rims at
   equal strength read as a box inside a box. */
.jform-panel::after {
    background: linear-gradient(145deg,
        rgba(255, 244, 214, 0.26) 0%,
        color-mix(in srgb, var(--panel-accent) 16%, transparent) 20%,
        rgba(212, 175, 55, 0.06) 50%,
        rgba(255, 255, 255, 0.02) 78%,
        rgba(255, 255, 255, 0.04) 100%);
}

/* --- BLOOM ---------------------------------------------------------------- */
.journey-panel::before,
.jform-panel::before {
    content: '';
    position: absolute;
    inset-inline: 0;
    top: 0;
    height: 14rem;
    border-radius: inherit;
    pointer-events: none;
    z-index: -1;
    background:
        radial-gradient(115% 100% at 50% -12%,
            color-mix(in srgb, var(--panel-accent) 20%, transparent) 0%,
            color-mix(in srgb, var(--panel-accent) 7%, transparent) 38%,
            transparent 72%);
    transition: background 0.45s ease;
}
.jform-panel::before {
    height: 7rem;
    background:
        radial-gradient(120% 100% at 50% -18%,
            color-mix(in srgb, var(--panel-accent) 12%, transparent) 0%,
            color-mix(in srgb, var(--panel-accent) 4%, transparent) 42%,
            transparent 74%);
}

/* --- Panel depth ---------------------------------------------------------- */
/* Matches .manual-modal-panel. The old shadow was lighter because the outer
   glow ring was doing the separating; the ring is dialled down below, so the
   shadow takes the job it does on every other panel. */
.journey-panel {
    box-shadow:
        0 40px 100px rgba(0, 0, 0, 0.8),
        0 0 0 1px rgba(212, 175, 55, 0.06),
        inset 0 1px 0 rgba(212, 175, 55, 0.2),
        inset 0 -1px 0 rgba(0, 0, 0, 0.4);
}
/* A blur(40px) halo the size of the panel is both the most expensive thing in
   this file and the loudest thing on screen. Kept as ambience only - the rim
   and the shadow are what separate the panel now. */
.journey-glow-ring {
    opacity: 0.28;
    filter: blur(52px);
}

/* --- Shimmer, tinted by the accent ---------------------------------------- */
.journey-shimmer-top {
    background: linear-gradient(90deg,
        transparent 0%,
        var(--panel-accent, #D4AF37) 20%,
        color-mix(in srgb, var(--panel-accent, #D4AF37) 35%, #FFF8DC) 50%,
        var(--panel-accent, #D4AF37) 80%,
        transparent 100%);
    box-shadow: 0 0 24px color-mix(in srgb, var(--panel-accent, #D4AF37) 62%, transparent);
    border-top-right-radius: 1rem;
    border-top-left-radius: 1rem;
    z-index: 4;
}
.journey-shimmer-bottom { border-radius: 0 0 1rem 1rem; z-index: 4; }

/* --- Close: a lit disc that rotates --------------------------------------- */
.journey-close {
    background:
        radial-gradient(circle at 50% 28%,
            rgba(248, 113, 113, 0.26) 0%,
            rgba(239, 68, 68, 0.12) 55%,
            rgba(239, 68, 68, 0.05) 100%);
    border: 1px solid rgba(239, 68, 68, 0.46);
    color: #FCA5A5;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.07);
    transition: transform 0.24s cubic-bezier(0.34, 1.4, 0.64, 1),
                background 0.2s ease,
                border-color 0.2s ease,
                color 0.2s ease,
                box-shadow 0.24s ease;
}
.journey-close:hover,
.journey-close:focus-visible {
    background:
        radial-gradient(circle at 50% 28%,
            rgba(252, 165, 165, 0.55) 0%,
            rgba(239, 68, 68, 0.34) 55%,
            rgba(220, 38, 38, 0.20) 100%);
    border-color: rgba(252, 165, 165, 0.9);
    color: #FFFFFF;
    box-shadow: 0 0 20px rgba(239, 68, 68, 0.45),
                inset 0 1px 0 rgba(255, 255, 255, 0.16);
    transform: rotate(90deg) scale(1.06);
    outline: none;
}
.journey-close:active { transform: rotate(90deg) scale(0.9); }
@media (prefers-reduced-motion: reduce) {
    .journey-close:hover,
    .journey-close:focus-visible,
    .journey-close:active { transform: none; }
}

/* --- Content above the bloom ---------------------------------------------- */
/* These are children of a panel that now isolates, so they say explicitly that
   they sit above the wash. */
.jform-panel-head { z-index: 1; }
.jform-fields,
.jform-status,
.jform-note { position: relative; z-index: 1; }

/* --- Keyboard focus ------------------------------------------------------- */
/* The inputs had a focus style; the controls around them did not, and
   `outline: none` elsewhere in this file removed the browser default. A form
   you cannot see your way through with a keyboard is not finished. */
.journey-submit:focus-visible,
.jfield-eye:focus-visible,
.forgot-pw:focus-visible,
.journey-back:focus-visible {
    outline: 2px solid rgba(var(--gold-rgb), 0.85);
    outline-offset: 2px;
}


/* --- Touch hit areas, journey modal ---------------------------------------
   Companion to the blocks at the foot of components.css and landing.css.
   Coarse pointer only; nothing here moves on a mouse. */
@media (hover: none) and (pointer: coarse) {
    .journey-close { width: 2.75rem; height: 2.75rem; }
    .jmcard-btn    { min-height: 44px; }
}


/* ============================================================================
   2026-08-22 - REGISTRATION: readability, contrast and density
   ----------------------------------------------------------------------------
   Measured on the register step at 1280x800 before any of this:
     - the info rail was 1,181px tall, exactly as tall as the form beside it,
       and 11 of its rows were reference data (7 speeds + 4 timeline events)
     - the third stat tile overflowed its own grid by 61px and was clipped to
       "1000000" / "SPEE"
     - 49 of 74 pieces of text sat on the 13px tier, including every field label
     - the input placeholders measured 3.92:1 and the quadrant coordinates
       3.98:1, both under the 4.5 floor
   ============================================================================ */

/* --- The clipped stat tile ------------------------------------------------ */
/*
 * `repeat(3, 1fr)` is not enough. A grid item's default `min-width` is `auto`,
 * which means "never smaller than my content", so `1fr` silently behaves as
 * `minmax(auto, 1fr)` and one long value pushes the whole row wider than its
 * container. minmax(0, 1fr) is what actually makes the three tracks equal and
 * lets the content shrink inside them.
 *
 * The value itself is now short anyway (format_speed_value gives "×1M"), but
 * this is the rule that stops any future value doing the same thing.
 */
.jic-stats { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.jic-stat-val { max-width: 100%; }

/* --- Folded reference sections -------------------------------------------- */
/* <details> in the rail. The summary reuses .jic-section-head so an open fold
   is indistinguishable from the old always-on section. */
.jic-fold { margin: 0; }
.jic-fold-head {
    cursor: pointer;
    list-style: none;
    position: relative;
    user-select: none;
    /* The head is a full-bleed bar; the caret needs room at the end so a long
       translated label cannot run under it. */
    padding-inline-end: 2.25rem;
    transition: color 0.2s ease, background 0.2s ease;
}
/* Both are needed: WebKit uses the pseudo-element, everyone else uses the
   property, and leaving either in place shows a second stray triangle. */
.jic-fold-head::-webkit-details-marker { display: none; }
.jic-fold-head::marker { content: ''; }
.jic-fold-head::after {
    content: '';
    position: absolute;
    top: 50%;
    inset-inline-end: 1.1rem;
    width: 0.5rem;
    height: 0.5rem;
    margin-top: -0.32rem;
    border-left: 1.5px solid currentColor;
    border-bottom: 1.5px solid currentColor;
    transform: rotate(45deg);
    opacity: 0.75;
    transition: transform 0.22s ease;
}
.jic-fold[open] > .jic-fold-head::after { transform: rotate(-135deg); }
.jic-fold-head:hover { color: #E8C86A; background: linear-gradient(180deg, rgba(10,7,3,0.75) 0%, rgba(20,14,7,0.5) 100%); }
.jic-fold-head:focus-visible { outline: 2px solid rgba(var(--gold-rgb), 0.85); outline-offset: -2px; }
@media (prefers-reduced-motion: reduce) {
    .jic-fold-head::after { transition: none; }
}

/* --- Field labels --------------------------------------------------------- */
/* NICKNAME / E-MAIL / PASSWORD were 13px. They label the only four things this
   screen actually asks for, and they were set smaller than the paragraph in the
   tribe panel below them. */
.jfield-label { font-size: 0.875rem; }

/* --- Placeholders --------------------------------------------------------- */
/* Measured 3.92:1 against the engraved field. A placeholder is a hint rather
   than content, but it is the only thing in an empty field and it has to be
   legible; this takes it to 5.6:1 while staying clearly lighter than the value
   the visitor types in (#F5E5A8). */
.engraved-input::placeholder { color: rgba(198, 172, 112, 0.86); }

/* --- Quadrant card, three lines in the right order ------------------------- */
/*
 * Each card says the same thing three ways: "NE", "North-East", "(+|+)". That
 * is defensible - the abbreviation is the target, the name is the plain
 * reading, the coordinates are the convention a returning player thinks in -
 * but only if they are ranked. Measured, they were not:
 *
 *   before      corner 7.67 | name 5.71 | coords 3.98   (coords failed 4.5)
 *   first try   corner 7.67 | name 5.71 | coords 7.71   (coords now OUTRANK
 *                                                        the name they support)
 *
 * The first fix cleared the floor and inverted the hierarchy, which is the same
 * mistake as a headline set darker than its own paragraph. The name is the line
 * that matters most after the abbreviation, so it goes up too, and the
 * coordinates land above the floor but below both.
 *
 * Solved rather than eyeballed. Both lines use the card's own sand tone
 * (214,192,150) over the measured panel backdrop, with the alpha binary-searched
 * for the target ratio - guessing produced an inverted ladder twice in a row.
 *
 *   corner "NE"        16px  7.67  (unchanged, it is the target you click)
 *   name  "North-East" 14px  7.00  (alpha 0.80)
 *   coords "(+|+)"     13px  5.20  (alpha 0.666, clear of the 4.5 floor)
 */
.pos-label  { font-size: 0.875rem; color: rgba(214, 192, 150, 0.80); }
.pos-coords { color: rgba(214, 192, 150, 0.666); }
.pos-card.is-selected .pos-label  { color: rgba(248, 234, 160, 0.96); }
.pos-card.is-recommended .pos-coords { color: rgba(110, 231, 183, 0.617); }

/* --- Tribe blurb ---------------------------------------------------------- */
/* Body copy on the 13px tier; the tribe name above it is Cinzel at 14px, so the
   description was competing with its own heading for smallest-thing-on-screen. */
.tribe-desc { font-size: 0.875rem; }


/* --- Show/hide password, and Back ------------------------------------------ */
/*
 * The eye toggle measured 16x16. It is a real control - the one that tells you
 * whether you typed your password correctly - and it was the smallest target on
 * the whole registration screen, sitting inside a 48px field with room going
 * spare. Padding only: the icon does not change size, the box around it does,
 * and the button stays centred on the field because it is still translated by
 * half its own height.
 *
 * Right offset drops from 0.75rem to 0.25rem so the bigger box keeps the ICON in
 * the same place rather than pushing it inward.
 */
.jfield-eye {
    left: 0.25rem;
    width: 2.25rem;
    height: 2.25rem;
    justify-content: center;
    border-radius: var(--radius-full);
    transition: color 0.2s ease, background 0.2s ease;
}
.jfield-eye:hover { background: rgba(var(--gold-rgb), 0.10); }
.jfield-eye:focus-visible {
    outline: 2px solid rgba(var(--gold-rgb), 0.85);
    outline-offset: -2px;
}
/* has-eye reserved 2.5rem of right padding for a 16px icon. The box is 36px
   now, so the text needs to clear that instead. */
.engraved-input.has-eye { padding-left: 3rem; }

@media (hover: none) and (pointer: coarse) {
    .jfield-eye   { width: 2.75rem; height: 2.75rem; left: 0.125rem; }
    .journey-back { min-height: 44px; }
    .engraved-input.has-eye { padding-left: 3.25rem; }
}


/* ============================================================================
   2026-08-22 - JOURNEY MODAL: elevation, orientation and selection
   ----------------------------------------------------------------------------
   Owner report: "everything is too dark and I am worried people cannot find
   their way around the page". Measured, that is exactly right. Every surface in
   the modal sat between #0a0703 and #1a130c, and the step between one stacked
   surface and the next was:

       page   -> panel   1.052
       panel  -> section 1.040
       section-> input   1.071

   A ratio of 1.05 between two dark greys is not an edge anyone can see. The
   whole modal was one flat black field with hairlines drawn on it, so there was
   nothing to tell you that a form section was a container, or that an input was
   a place to type. Depth was being carried entirely by 1px borders.

   Below is an actual elevation ladder. Each level is a real step, and the input
   deliberately goes DOWN from its container so it reads as a well you type into
   rather than another panel:

       page    #0a0703
       panel   #1d160e   step 1.121 from page
       section #2e2417   step 1.173 from panel
       input   #0c0805   step 1.304 down from section
       tile    #1f1810   step 1.147 down from section

   ⚠ Raising a background lowers the contrast of the light text on it. Every
   text ratio was re-measured after this change, not assumed.
   ============================================================================ */

.journey-root {
    --jm-page:    #0a0703;
    --jm-panel:   rgba(30, 23, 15, 0.93);
    --jm-section: rgba(50, 39, 25, 0.80);
    --jm-input:   rgba(6, 4, 2, 0.86);
    --jm-tile:    rgba(0, 0, 0, 0.32);
}

.journey-panel {
    background: linear-gradient(170deg,
        var(--jm-panel) 0%,
        rgba(24, 18, 12, 0.94) 60%,
        rgba(28, 21, 14, 0.93) 100%);
}
.jform-panel {
    background: linear-gradient(165deg,
        var(--jm-section) 0%,
        rgba(38, 30, 19, 0.84) 100%);
}
.jic {
    background: linear-gradient(165deg,
        var(--jm-section) 0%,
        rgba(34, 26, 17, 0.84) 100%);
}
.engraved-input { background: var(--jm-input); }
.jic-stat, .pos-card, .jmcard-stat { background: var(--jm-tile); }

/* The section heading was a bar the same colour as everything else. Now that the
   section it sits on is lighter, the heading goes the other way and reads as a
   lid on the box. */
.jform-panel-head {
    background: linear-gradient(180deg, rgba(8, 6, 3, 0.86) 0%, rgba(16, 12, 7, 0.62) 100%);
}

/* ============================================================================
   The rail: sized to its content, and it follows you down the form
   ----------------------------------------------------------------------------
   `height: 100%` stretched the rail card to whatever the form beside it
   happened to be. That was tolerable when the rail carried a seven-row speed
   table and a four-event timeline; once both were folded away the card was
   330px of content in a 1,190px box, and the bottom two thirds were an empty
   field with a faint helmet in it. The owner's words: "the left side looks too
   empty when the times are closed".

   Two changes, and the second is the point of the first: the card now sizes to
   its content, and it STICKS to the top of the scroller. Instead of a long
   empty column you get a compact card that stays with you while you fill in a
   form that is three screens long - so which realm you are joining is on screen
   at the moment you press the button, which it was not before.
   ============================================================================ */
@media (min-width: 1024px) {
    .journey-register-grid { align-items: start; }
    .journey-info-rail > div { height: auto; }
    .journey-info-rail {
        position: sticky;
        /* Clears the modal's own top padding so the card does not touch the
           panel edge when it parks. */
        top: 0.25rem;
    }
    .jic { height: auto; min-height: 0; }
}

/* ============================================================================
   Tribe portraits
   ============================================================================ */
.tribe-portrait {
    display: flex;
    align-items: flex-end;
    justify-content: center;
    /* Fixed height, free width: the five portraits run 100-166px wide at the
       same 150px height, so anchoring the height keeps every figure standing on
       the same line whatever their build. */
    height: 4.5rem;
    margin-bottom: 0.5rem;
}
.tribe-portrait img {
    height: 100%;
    width: auto;
    max-width: 100%;
    object-fit: contain;
    /* The art is a flat cartoon figure on transparency. A drop shadow lifts it
       off the card the way the hero mark is lifted off the artwork, and the
       slight desaturation stops three differently-lit sprites from fighting
       each other. Selecting a tribe restores it to full colour. */
    filter: saturate(0.86) drop-shadow(0 3px 6px rgba(0, 0, 0, 0.55));
    /*
     * --portrait-nudge centres the figure rather than the image box. Set per
     * tribe in components/journey-modal.phtml from the measured alpha-weighted
     * centroid of each PNG; the Teuton's sword arm reaches so far right that his
     * body sat 10.9px (6.6%) left of his own box centre, which is what read as
     * "the Teuton is not centred".
     *
     * Applied on the image, not the box, so the card's own layout is untouched.
     */
    transform: translateX(var(--portrait-nudge, 0));
    transition: filter 220ms ease, transform 220ms ease;
}
.tribe-card:hover .tribe-portrait img {
    transform: translateX(var(--portrait-nudge, 0)) translateY(-2px);
}
.tribe-card.is-selected .tribe-portrait img {
    filter: saturate(1.04) drop-shadow(0 4px 10px rgba(0, 0, 0, 0.6));
}
@media (prefers-reduced-motion: reduce) {
    .tribe-portrait img { transition: none; }
    /* Keeps the centring correction; only the hover lift is dropped. */
    .tribe-card:hover .tribe-portrait img { transform: translateX(var(--portrait-nudge, 0)); }
}

/* ============================================================================
   Starting position: make the choice obvious
   ----------------------------------------------------------------------------
   The selected quadrant was a faint gold wash (0.14 alpha) plus a small dot,
   and the CSS HID that dot whenever the card was also the recommended one. The
   recommended quadrant is the one most people click, so the most common
   selection on the screen was the one with no selected marker at all - which is
   the owner's report: "when RANDOM is selected it is hard to notice".

   Selection is now carried by the card, not by a badge: a real fill, a 2px rim
   and a lit edge. The badge comes back as well, moved to the leading corner so
   it no longer collides with the recommended ribbon on the trailing one.
   ============================================================================ */
.pos-card.is-selected {
    background: linear-gradient(165deg, rgba(212, 175, 55, 0.30) 0%, rgba(212, 175, 55, 0.14) 100%);
    border: 2px solid rgba(244, 226, 135, 0.92);
    box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5),
                0 0 22px rgba(212, 175, 55, 0.30),
                inset 0 1px 0 rgba(255, 244, 214, 0.22);
    /* The 2px border would otherwise shift the content by 1px against its
       unselected neighbours. */
    padding: calc(0.5rem - 1px);
}
/* The tick, in the LEADING corner. The old dot lived on the trailing corner and
   was suppressed to make room for the recommended ribbon; nothing has to be
   suppressed now because they no longer share a corner. */
.pos-card.is-selected::after {
    content: '';
    position: absolute;
    top: 0.375rem;
    inset-inline-start: 0.375rem;
    width: 1.05rem;
    height: 1.05rem;
    border-radius: var(--radius-full);
    background: linear-gradient(180deg, #FFF3C4 0%, #E6C347 100%);
    /* Tick drawn with a mask so it needs no extra element and inherits the
       rounded chip behind it. */
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M3 6.2L5 8.2L9 3.8' stroke='%23000' stroke-width='1.8' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 0.75rem 0.75rem no-repeat,
                 linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
            mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M3 6.2L5 8.2L9 3.8' stroke='%23000' stroke-width='1.8' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 0.75rem 0.75rem no-repeat,
                 linear-gradient(#000 0 0);
            mask-composite: exclude;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.6);
}
/* The old dot is redundant now and would sit under the ribbon. */
.pos-dot { display: none !important; }

/* RANDOM belongs to the same control group, so it answers in the same colour.
   It was grey selected and slightly-less-grey unselected - two shades of the
   same neutral, next to four cards that turn gold. */
.pos-random.is-selected {
    background: linear-gradient(180deg, rgba(212, 175, 55, 0.34) 0%, rgba(212, 175, 55, 0.16) 100%);
    border: 2px solid rgba(244, 226, 135, 0.92);
    color: #FFF3C4;
    box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5),
                0 0 22px rgba(212, 175, 55, 0.30),
                inset 0 1px 0 rgba(255, 244, 214, 0.22);
    padding: calc(0.625rem - 1px);
}
.pos-random.is-selected svg { color: #FFF3C4; }
.pos-random:focus-visible,
.pos-card:focus-visible {
    outline: 2px solid rgba(var(--gold-rgb), 0.9);
    outline-offset: 2px;
}


/* ============================================================================
   2026-08-22 - LOGIN VIEW, brought onto the same footing as registration
   ----------------------------------------------------------------------------
   Everything the register pass fixed that lives on shared classes - the
   elevation ladder, 14px field labels, the 6.8:1 placeholder, the 48px inputs,
   the 36px show-password button, focus rings - the login view already inherited.
   What it did NOT inherit was structure, because its markup was different:
   a bare stack of fields sitting directly on the modal panel, with a decorative
   rule for a heading instead of the panel head every other section uses.

   The markup now mirrors registration (fieldset.jform-panel + legend), so the
   rim, the bloom and the heading bar come for free. What is left here is the
   measurement the two views still got wrong in the same way.
   ============================================================================ */

/* --- Line length ----------------------------------------------------------
 * Measured: the login inputs were 677px wide and the register inputs 742px.
 * That is a text box roughly three times wider than the longest thing anyone
 * types into it - a 15-character nickname - and it costs twice: the caret
 * starts a long way from the label that names it, and a field that wide reads
 * as an empty container rather than as a place to type one word.
 *
 * The column is capped and centred rather than capped and left-aligned, so the
 * space that opens up is symmetric and reads as a deliberate column instead of
 * a field that failed to stretch.
 *
 * Applied to .jform-fields, which is the group BOTH views use, so the two
 * cannot drift apart again. The tribe and position grids are untouched: those
 * are card grids that genuinely want the full width.
 */
.jform-fields {
    max-width: 32rem;
    margin-inline: auto;
}

/*
 * Login is a two-field form. Left at the full width of its grid columns the
 * panel was 719px wide wrapping a 512px column, which put more empty panel on
 * screen than form. The whole view is capped instead, so panel, fields and
 * button share one edge.
 */
.jform-login {
    /*
     * ⚠ NOT `max-width` + `margin-inline: auto`. This is a GRID ITEM, and an
     * auto inline margin switches a grid item from stretch to fit-content -
     * so the form stopped filling its two columns and collapsed to the
     * intrinsic width of its own contents (measured 340px, narrower than a
     * single column). Same trap .jmcard-actions documents a few hundred lines
     * up: an auto margin does not just centre, it changes how the box is sized.
     *
     * width: min() keeps the box sized against its grid area and capped, and
     * justify-self does the centring without touching sizing.
     */
    width: min(34rem, 100%);
    justify-self: center;
}
/* The register CTA is a full-width mega-button by design and stays that way.
   Login's button belongs to its field column, so it matches it. */
.jform-login .journey-submit {
    /* Block-level child of the form, not a grid item, so auto margins are safe
       here - it is only the grid item above that had to change. */
    max-width: 32rem;
    margin-inline: auto;
}

/* --- Forgot password ------------------------------------------------------
 * An 18px target, and the only escape route for someone who cannot get in. */
.forgot-pw { margin-top: 0; }
.forgot-pw a {
    display: inline-flex;
    align-items: center;
    padding-block: 0.375rem;
    border-radius: var(--radius-sm);
}
.forgot-pw a:focus-visible {
    outline: 2px solid rgba(var(--gold-rgb), 0.85);
    outline-offset: 2px;
}
@media (hover: none) and (pointer: coarse) {
    /* 0.6875rem landed on 42px against a 20px text box; 0.75rem is 44 exactly,
       the same correction the footer links needed. */
    .forgot-pw a { padding-block: 0.75rem; }
}

/* --- The chosen realm card ------------------------------------------------
 * `align-items: stretch` made the card as tall as the form beside it. That was
 * harmless while the form was a tall bare stack; now that the form is a
 * compact panel the card would stretch to match and gain a dead zone at the
 * bottom - the same fault the register rail had. It sizes to its content and
 * sits at the top instead.
 */
@media (min-width: 768px) {
    .journey-login-grid { align-items: start; }
}


/* ============================================================================
   2026-08-22 - Panel heading centring, and the server stat chips
   ============================================================================ */

/* --- The heading was 16px off centre --------------------------------------
 * Measured on the login panel: panel centre 824, heading centre 808.
 *
 * The rule was `margin: -1rem -1rem 0.75rem`, written when this was meant to be
 * a full-bleed bar pulled out over the panel padding. It never became one - a
 * <legend> is shrink-to-fit, so it stayed a pill - and the two negative side
 * margins do not cancel on it the way they would on a normal block. The start
 * margin moves the box; the end margin only feeds the shrink-to-fit width. Net
 * result: pulled 16px toward the start, on every panel in the modal.
 *
 * `auto` on both sides centres the box explicitly instead of relying on how a
 * legend happens to be placed. The top margin is kept - that one is doing real
 * work, lifting the heading onto the panel's top edge.
 */
.jform-panel-head {
    margin: -1rem auto 0.75rem;
}

/* --- Server stat chips: raised, not sunk ----------------------------------
 * Players / Online / Speed sat in tiles that measured 1.09 against the card
 * behind them - so the boxes had no edge, and the numbers read as floating on
 * the card rather than sitting in anything. The text itself was never the
 * problem: every label already measured 6.2:1 or better.
 *
 * They are chips now, not wells. A stat is a thing being displayed, so it
 * belongs ABOVE its surface; an input is a thing you fill, so it belongs below
 * one. Solved for a 1.28 step, which is the point where the box reads as an
 * object without the gold label dropping under 6:1 (it lands at 6.34).
 *
 * ⚠ .pos-card deliberately does NOT get this. It is a control whose selected
 * state is a bright gold fill, and starting it raised would leave nowhere for
 * the selected state to go.
 */
.journey-root { --jm-chip: rgba(92, 74, 46, 0.40); }

.jmcard-stat,
.jic-stat {
    background: var(--jm-chip);
    /* Reads as lit from above: a hairline on the top edge, a soft shadow under
       the box. Cheap - no filter, no extra element. */
    box-shadow: inset 0 1px 0 rgba(255, 240, 200, 0.10),
                0 2px 6px rgba(0, 0, 0, 0.35);
}

/* The accent borders were 0.20 alpha, which at this size is a suggestion rather
   than an edge. Raised so each chip is clearly its own box and keeps its
   meaning-colour. */
.jmcard-stat-players { border-color: rgba(212, 175, 55, 0.42); }
.jmcard-stat-online  { border-color: rgba(52, 211, 153, 0.42); }
.jmcard-stat-speed   { border-color: rgba(248, 113, 113, 0.42); }

/* The rail's meta tiles (Map Size / Protection) are the same class of object
   and were left behind by the same amount. */
.jic-meta-tile {
    background: var(--jm-chip);
    box-shadow: inset 0 1px 0 rgba(255, 240, 200, 0.10),
                0 2px 6px rgba(0, 0, 0, 0.35);
}


/* ============================================================================
   2026-08-22 - Rail chip captions, and a tick on RANDOM
   ============================================================================ */

/* --- PLAYERS / ONLINE / SPEED / MAP SIZE / PROTECTION ----------------------
 * Owner: "on the registration page you cannot read the words PLAYERS, MAP SIZE
 * and PROTECTION". Measured against the chip they sit on, it was two separate
 * faults:
 *
 *   MAP SIZE / PROTECTION   2.61:1   - a real failure, and it PREDATES my work.
 *                                      rgba(gold, 0.5) was never readable; it
 *                                      measured 2.73 on the old background too.
 *   PLAYERS                 5.83:1   - technically passing, but I made it worse:
 *   ONLINE                  6.38:1     lightening these into chips cost about
 *   SPEED                   6.46:1     0.55 across all three (was 6.36/6.95/7.04).
 *
 * The number and its caption were also the SAME colour, so the caption read as
 * nothing more than a smaller copy of the number - and gold text on a warm gold
 * chip has almost no hue separation to help it.
 *
 * Split by role instead: the VALUE keeps the meaning-colour and is brightened so
 * it stays top of the ladder; the CAPTION goes to a neutral warm tone, which
 * separates by hue as well as by luminance.
 *
 *   value   18px accent    7.8 - 8.0
 *   caption 13px neutral   7.4
 */
.jic-stat-players { color: #F0CE5E; }   /* 5.83 -> 7.99 */
.jic-stat-online  { color: #6EE7B7; }   /* 6.38 -> 8.04 */
.jic-stat-speed   { color: #FEBEBE; }   /* 6.46 -> 7.78 */

.jic-stat-lbl,
.jic-meta-lbl {
    /* 2.61 (meta) and 5.83 (stat) -> 7.36, and no longer the same colour as the
       figure above it. */
    color: rgba(214, 199, 170, 1);
}
.jic-meta-val { color: #F0CE5E; }

/* --- RANDOM gets the same tick as the quadrants ---------------------------
 * It already turns gold with a 2px rim, but the four quadrants each show a tick
 * as well, so RANDOM was the one option in the group that announced itself
 * differently. Same chip, same mask, same corner - it just sits vertically
 * centred because this control is a single wide row rather than a square.
 */
.pos-random { position: relative; }
.pos-random.is-selected::after {
    content: '';
    position: absolute;
    top: 50%;
    inset-inline-start: 0.625rem;
    width: 1.05rem;
    height: 1.05rem;
    margin-top: -0.525rem;
    border-radius: var(--radius-full);
    background: linear-gradient(180deg, #FFF3C4 0%, #E6C347 100%);
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M3 6.2L5 8.2L9 3.8' stroke='%23000' stroke-width='1.8' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 0.75rem 0.75rem no-repeat,
                 linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
            mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M3 6.2L5 8.2L9 3.8' stroke='%23000' stroke-width='1.8' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 0.75rem 0.75rem no-repeat,
                 linear-gradient(#000 0 0);
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.6);
    pointer-events: none;
}
/* The label is centred in the button, so it needs to clear the tick rather than
   run under it. */
.pos-random.is-selected { padding-inline-start: 2.25rem; }

/* ============================================================================
   REALM PICKER AS ROWS  (.journey-server-grid.is-rows)
   ----------------------------------------------------------------------------
   The select view used to be three tall columns. Three cards ~480px high inside
   a panel capped at 90vh meant the picker scrolled on any laptop, and the one
   thing a player is actually doing here, comparing realms, was the hardest
   thing to do, because each figure sat at a different height in a different
   column.

   The landing page's "Game Worlds" band already solved this: the SAME card
   markup re-laid out as horizontal rows with declared track widths, so every
   realm's figures line up in one column down the stack. This is that layout,
   ported to the modal card, with one addition the landing rows do not need.
   The row keeps its own action buttons, because the modal is where a player
   logs in or registers and pushing that behind a second click would cost the
   returning player the shortest path they have today.

   Scoped to `.is-rows` on purpose. `render_modal_server_card()` is also
   called by the LOGIN view (mode 'static', inside .journey-login-slot) where
   the card must stay a vertical card. Nothing here can reach it.

   Each .jmcard is its OWN grid, so `auto` tracks would settle at a different
   width per realm and the columns would not line up between rows, the exact
   fault the landing band was rebuilt to fix. Every shared track is therefore a
   DECLARED width; only the identity column flexes.
   ========================================================================== */

.journey-server-grid.is-rows {
    flex-direction: column;
    flex-wrap: nowrap;
    align-items: stretch;
    gap: 0.625rem;
}
.journey-server-grid.is-rows > .jmcard {
    flex: 0 0 auto;
    max-width: none;
    width: 100%;
}

/* The prime card is lifted above its neighbours in a ROW of cards. In a
   vertical stack that lift just breaks the rhythm, so it comes off; the gold
   border, ribbon and halo still mark the realm. */
@media (min-width: 701px) {
    .journey-server-grid.is-rows .jmcard--prime,
    .journey-server-grid.is-rows .jmcard--prime:hover { transform: none; }
}

/* --- Elevation: the row has to sit ON the panel, not in it -----------------
   The card's background is a gradient from rgba(38,28,18) down to
   rgba(18,14,9). Over a 480px column that reads as depth, and the light top
   of the gradient is most of what you see. A 70px row shows mostly the
   BOTTOM of that ramp, which is DARKER than the panel behind it
   (--jm-panel, rgba(30,23,15,0.93)) - so the row stopped reading as a surface
   at all and merged into the modal.

   The modal already carries the elevation ladder the rest of this pass uses:
   page -> panel -> section -> chip. A row is a section, so it takes
   --jm-section and the gradient becomes a slight sheen on top of that rather
   than the whole surface. The blue / red / slate states get the same lift in
   their own hue, so all rows sit on one plane.
   ========================================================================== */

@media (min-width: 768px) {
    .journey-server-grid.is-rows .jmcard {
        background:
            linear-gradient(165deg, rgba(255, 236, 190, 0.05) 0%, rgba(0, 0, 0, 0.05) 100%),
            var(--jm-section);
    }
    .journey-server-grid.is-rows .jmcard--launching {
        background:
            linear-gradient(165deg, rgba(191, 219, 254, 0.06) 0%, rgba(0, 0, 0, 0.05) 100%),
            rgba(34, 47, 76, 0.86);
    }
    .journey-server-grid.is-rows .jmcard--soon {
        background:
            linear-gradient(165deg, rgba(254, 202, 202, 0.06) 0%, rgba(0, 0, 0, 0.05) 100%),
            rgba(66, 28, 25, 0.86);
    }
    .journey-server-grid.is-rows .jmcard--dev {
        background:
            linear-gradient(165deg, rgba(203, 213, 225, 0.05) 0%, rgba(0, 0, 0, 0.05) 100%),
            rgba(40, 47, 57, 0.86);
    }
    /* A sealed slot is the one row that SHOULD sink: it is not a realm you can
       reach, and lifting it to the same plane as the others would promise
       something that is not there. */
    .journey-server-grid.is-rows .jmcard--sealed {
        background: linear-gradient(165deg, rgba(16, 13, 9, 0.80) 0%, rgba(10, 8, 5, 0.86) 100%);
    }

    /* Now that the row is a lit surface, its resting border can come down and
       let the fill carry the edge; hover puts it back. Without this the gold
       rim reads as a second, competing signal on every row at once. */
    .journey-server-grid.is-rows .jmcard { border-color: rgba(212, 175, 55, 0.16); }
    .journey-server-grid.is-rows .jmcard--prime { border-color: rgba(245, 158, 11, 0.42); }
}

/* --- The row itself (desktop) ------------------------------------------- */

@media (min-width: 1000px) {
    .journey-server-grid.is-rows .jmcard {
        display: grid;
        /* dot | identity | status+pills | figures | actions */
        grid-template-columns: 0.75rem minmax(9rem, 1fr) 13rem 17rem 15rem;
        grid-template-rows: auto auto;
        align-items: center;
        column-gap: 1.25rem;
        row-gap: 0.1rem;
        /* .jmcard-body carried this padding; `display: contents` throws a box's
           own padding away, so it moves up to the card. */
        padding: 0.7rem 1.25rem;
    }
    .journey-server-grid.is-rows .jmcard:hover { transform: translateY(-2px); }

    /* Dissolve the two wrappers so their children become row cells. Safe here
       because neither carries a background, border or padding of its own once
       the padding above has moved to .jmcard. */
    .journey-server-grid.is-rows .jmcard-body,
    .journey-server-grid.is-rows .jmcard-head { display: contents; }

    /* .jmcard-body used to hold `z-index: 1` to sit above the sealed hatch.
       That box no longer exists, so the hatch drops behind instead. The card
       already makes a stacking context (backdrop-filter), so -1 stays inside
       the card and still paints above its own background. */
    .journey-server-grid.is-rows .jmcard-hatch { z-index: -1; }

    /* column 1: the live dot, centred against both lines */
    .journey-server-grid.is-rows .jmcard-livedot {
        grid-column: 1;
        grid-row: 1 / span 2;
        margin-top: 0;
        justify-self: center;
    }

    /* column 2: ribbon over round.
       The ribbon stops being a full-width banner and becomes the row's eyebrow,
       the same shape the landing rows use. Only the chrome comes off; the
       colour variants (.jmcard-banner--red / --blue) still apply. */
    .journey-server-grid.is-rows .jmcard-prime-banner {
        grid-column: 2;
        grid-row: 1;
        justify-content: flex-start;
        gap: 0.4rem;
        padding: 0;
        background: none;
        border-bottom: 0;
        font-size: 0.6875rem;
        letter-spacing: 0.2em;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }
    .journey-server-grid.is-rows .jmcard-prime-banner .jmcard-prime-dot:last-child { display: none; }

    .journey-server-grid.is-rows .jmcard-head-text {
        grid-column: 2;
        grid-row: 2;
        min-width: 0;
        text-align: right;
    }
    /* "Nexian Official" is on every row, directly under a logo that says
       Nexian. That is the same reasoning render_server_card() applies to the
       landing rows, where the brand word is trimmed off the name. The round
       label is the line that actually identifies the realm. */
    .journey-server-grid.is-rows .jmcard-name,
    .journey-server-grid.is-rows .jmcard-desc { display: none; }
    .journey-server-grid.is-rows .jmcard-subtitle {
        margin: 0;
        font-size: 1.0625rem;
        line-height: 1.2;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    /* column 3: the realm's own state, on the same two lines as the identity.
       Row 1 carries whichever badge this state has, row 2 the day/phase or
       map/speed pills. The two badges are mutually exclusive by construction:
       .jmcard-status-pill only renders for launching / soon / dev, and
       .jmcard-closed-pill only for a LIVE realm with registration closed. So
       one declared cell serves both and neither can ever land on the other.
       Every row therefore has the same two lines, and the rows come out the
       same height without a min-height guess that a longer language would
       break. */
    .journey-server-grid.is-rows .jmcard-status-pill,
    .journey-server-grid.is-rows .jmcard-closed-pill {
        grid-column: 3;
        grid-row: 1;
        justify-self: start;
        align-self: center;
        margin: 0;
    }
    .journey-server-grid.is-rows .jmcard-pills {
        grid-column: 3;
        grid-row: 2;
        margin-bottom: 0;
        min-height: 0;
        gap: 0.3rem;
        align-content: center;
    }
    .journey-server-grid.is-rows .jmcard-pill {
        font-size: 0.6875rem;
        letter-spacing: 0.08em;
        padding: 0.1rem 0.5rem;
    }
    .journey-server-grid.is-rows .jmcard-closed-pill {
        font-size: 0.625rem;
        letter-spacing: 0.06em;
        padding: 0.15rem 0.5rem;
    }

    /* column 4: the figures, whatever form they take */
    .journey-server-grid.is-rows .jmcard-stats,
    .journey-server-grid.is-rows .jmcard-countdown,
    .journey-server-grid.is-rows .jmcard-soonblock,
    .journey-server-grid.is-rows .jmcard-devblock,
    .journey-server-grid.is-rows .jmcard-comingsoon {
        grid-column: 4;
        grid-row: 1 / span 2;
        height: auto;
        margin-bottom: 0;
    }
    /* In a row the boxed stat tiles read as three buttons competing with the
       row itself, so the chrome comes off and the numerals carry it. That is
       the same call the landing rows make. */
    .journey-server-grid.is-rows .jmcard-stats {
        grid-template-columns: repeat(3, minmax(0, 1fr));
        gap: 0.75rem;
    }
    .journey-server-grid.is-rows .jmcard-stat {
        background: none;
        border: 0;
        box-shadow: none;
        padding: 0;
    }
    /* The label already says what the figure is; the icon only competed with
       the number for the same slot. */
    .journey-server-grid.is-rows .jmcard-stat > svg { display: none; }
    .journey-server-grid.is-rows .jmcard-stat-val {
        font-size: 1.375rem;
        margin-bottom: 0.15rem;
        font-variant-numeric: tabular-nums;
    }
    .journey-server-grid.is-rows .jmcard-stat-lbl { font-size: 0.6875rem; }

    /* The countdown / soon / maintenance blocks keep their well, just at row
       height instead of the 5.5rem the three columns needed to stay level. */
    .journey-server-grid.is-rows .jmcard-countdown,
    .journey-server-grid.is-rows .jmcard-soonblock,
    .journey-server-grid.is-rows .jmcard-devblock {
        padding: 0.4rem 0.5rem;
        gap: 0.1rem;
    }
    .journey-server-grid.is-rows .jmcard-countdown-lbl,
    .journey-server-grid.is-rows .jmcard-soonblock-lbl { font-size: 0.6875rem; letter-spacing: 0.22em; }
    .journey-server-grid.is-rows .jmcard-countdown-val { font-size: 1.375rem; }
    .journey-server-grid.is-rows .jmcard-countdown-val.is-tba { font-size: 1rem; }
    .journey-server-grid.is-rows .jmcard-soonblock-val { font-size: 1.375rem; }
    .journey-server-grid.is-rows .jmcard-devblock-icon { display: none; }
    .journey-server-grid.is-rows .jmcard-devblock-text { font-size: 0.875rem; }
    .journey-server-grid.is-rows .jmcard-devblock-sub  { font-size: 0.6875rem; }
    .journey-server-grid.is-rows .jmcard-comingsoon { flex-direction: row; gap: 0.6rem; }
    .journey-server-grid.is-rows .jmcard-comingsoon-orb { width: 1.75rem; height: 1.75rem; }

    /* column 5: the actions */
    .journey-server-grid.is-rows .jmcard-actions {
        grid-column: 5;
        grid-row: 1 / span 2;
        margin-top: 0;
        gap: 0.5rem;
    }
    .journey-server-grid.is-rows .jmcard-btn {
        /* Shorter than the stacked card's button but never below the 44px
           touch floor, which is what sets this height rather than the text. */
        min-height: 2.75rem;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        padding: 0.4rem 0.5rem;
        font-size: 0.8125rem;
        letter-spacing: 0.1em;
    }
    /* Lifting a button inside a row nudges its neighbours; brightness alone
       reads as well and moves nothing. */
    .journey-server-grid.is-rows .jmcard-btn:hover { transform: none; filter: brightness(1.12); }

    /* The rule under the head only ever made sense in the stacked form. */
    .journey-server-grid.is-rows .jmcard-divider { display: none; }
}

/* --- Narrow desktop / tablet landscape: 768-999px -------------------------
   Below 1000px the five declared tracks no longer fit the panel. The day and
   phase pills are the one column a player can lose and still choose a realm,
   so they go and the row keeps identity, figures and actions. */

@media (min-width: 768px) and (max-width: 999px) {
    .journey-server-grid.is-rows .jmcard {
        display: grid;
        grid-template-columns: 0.75rem minmax(7rem, 1fr) 14rem 13rem;
        grid-template-rows: auto auto;
        align-items: center;
        column-gap: 1rem;
        row-gap: 0.1rem;
        padding: 0.7rem 1rem;
    }
    .journey-server-grid.is-rows .jmcard:hover { transform: translateY(-2px); }
    .journey-server-grid.is-rows .jmcard-body,
    .journey-server-grid.is-rows .jmcard-head { display: contents; }
    .journey-server-grid.is-rows .jmcard-hatch { z-index: -1; }

    .journey-server-grid.is-rows .jmcard-livedot {
        grid-column: 1; grid-row: 1 / span 2; margin-top: 0; justify-self: center;
    }
    .journey-server-grid.is-rows .jmcard-prime-banner {
        grid-column: 2; grid-row: 1;
        justify-content: flex-start; gap: 0.4rem; padding: 0;
        background: none; border-bottom: 0;
        font-size: 0.625rem; letter-spacing: 0.16em;
        white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    }
    .journey-server-grid.is-rows .jmcard-prime-banner .jmcard-prime-dot:last-child { display: none; }
    .journey-server-grid.is-rows .jmcard-head-text { grid-column: 2; grid-row: 2; min-width: 0; text-align: right; }
    .journey-server-grid.is-rows .jmcard-name,
    .journey-server-grid.is-rows .jmcard-desc,
    .journey-server-grid.is-rows .jmcard-pills,
    .journey-server-grid.is-rows .jmcard-divider { display: none; }
    .journey-server-grid.is-rows .jmcard-subtitle {
        margin: 0; font-size: 0.9375rem; line-height: 1.2;
        overflow: hidden; text-overflow: ellipsis;
    }
    /* No pill column at this width, so the status pill rides with the round. */
    .journey-server-grid.is-rows .jmcard-status-pill {
        grid-column: 2; grid-row: 2; justify-self: end; margin-top: 0;
    }
    .journey-server-grid.is-rows .jmcard-closed-pill {
        grid-column: 2; grid-row: 2; justify-self: end;
        margin: 0; font-size: 0.5625rem; padding: 0.1rem 0.4rem;
    }

    .journey-server-grid.is-rows .jmcard-stats,
    .journey-server-grid.is-rows .jmcard-countdown,
    .journey-server-grid.is-rows .jmcard-soonblock,
    .journey-server-grid.is-rows .jmcard-devblock,
    .journey-server-grid.is-rows .jmcard-comingsoon {
        grid-column: 3; grid-row: 1 / span 2; height: auto; margin-bottom: 0;
    }
    .journey-server-grid.is-rows .jmcard-stats { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0.5rem; }
    .journey-server-grid.is-rows .jmcard-stat { background: none; border: 0; box-shadow: none; padding: 0; }
    .journey-server-grid.is-rows .jmcard-stat > svg { display: none; }
    .journey-server-grid.is-rows .jmcard-stat-val { font-size: 1.125rem; margin-bottom: 0.1rem; font-variant-numeric: tabular-nums; }
    .journey-server-grid.is-rows .jmcard-stat-lbl { font-size: 0.625rem; letter-spacing: 0.06em; }
    .journey-server-grid.is-rows .jmcard-countdown,
    .journey-server-grid.is-rows .jmcard-soonblock,
    .journey-server-grid.is-rows .jmcard-devblock { padding: 0.35rem 0.4rem; gap: 0.1rem; }
    .journey-server-grid.is-rows .jmcard-countdown-lbl,
    .journey-server-grid.is-rows .jmcard-soonblock-lbl { font-size: 0.625rem; letter-spacing: 0.16em; }
    .journey-server-grid.is-rows .jmcard-countdown-val { font-size: 1.125rem; }
    .journey-server-grid.is-rows .jmcard-countdown-val.is-tba { font-size: 0.875rem; }
    .journey-server-grid.is-rows .jmcard-soonblock-val { font-size: 1.125rem; }
    .journey-server-grid.is-rows .jmcard-devblock-icon { display: none; }
    .journey-server-grid.is-rows .jmcard-devblock-text { font-size: 0.8125rem; }
    .journey-server-grid.is-rows .jmcard-devblock-sub  { font-size: 0.625rem; }
    .journey-server-grid.is-rows .jmcard-comingsoon { flex-direction: row; gap: 0.5rem; }
    .journey-server-grid.is-rows .jmcard-comingsoon-orb { width: 1.5rem; height: 1.5rem; }

    .journey-server-grid.is-rows .jmcard-actions { grid-column: 4; grid-row: 1 / span 2; margin-top: 0; gap: 0.4rem; }
    .journey-server-grid.is-rows .jmcard-btn {
        min-height: 2.75rem;
        display: inline-flex; align-items: center; justify-content: center;
        padding: 0.35rem 0.4rem; font-size: 0.75rem; letter-spacing: 0.06em;
    }
    .journey-server-grid.is-rows .jmcard-btn:hover { transform: none; filter: brightness(1.12); }
}

/* --- Tooltips on a row -----------------------------------------------------
   The disabled buttons (login closed, registration closed, maintenance) explain
   themselves through `data-tooltip`, drawn as a ::after ABOVE the button.

   That worked in a 480px-tall card, where the tooltip still landed inside the
   card's own box. A row is ~70px tall, so the tooltip starts outside it
   immediately and `overflow: hidden` on .jmcard cut it in half. Centring made
   it worse: the buttons sit in the last column, so a ~300px tooltip centred on
   a 115px button hangs ~90px past the card's right edge, and .journey-scroll
   (overflow-x: hidden) takes the rest off.

   Both faults are geometry, so both are fixed by geometry: let the row show
   what leaves it, and anchor the tooltip to the button's inline END so it can
   only ever grow back INTO the panel.
   ========================================================================== */

@media (min-width: 768px) {
    .journey-server-grid.is-rows .jmcard { overflow: visible; }
    /* .jmcard's `overflow: hidden` was also what kept the sealed hatch inside
       the rounded corners. The hatch already carries the same radius itself,
       so it does not need the card to clip it. */
    .journey-server-grid.is-rows .jmcard-hatch { border-radius: 0.75rem; }

    /* A hovered row lifts 2px and its tooltip is drawn over the row above it,
       so it has to outrank its neighbours for that moment. .jmcard is already
       position: relative, so this is the only declaration needed. */
    .journey-server-grid.is-rows .jmcard:hover,
    .journey-server-grid.is-rows .jmcard:focus-within { z-index: 5; }

    .journey-server-grid.is-rows .jmcard-btn[data-tooltip]::after {
        right: auto;
        left: 0;
        transform: translateY(4px);
        /* A long sentence in a wider language would otherwise run off the left
           of the panel instead of the right. Wrapping is the honest answer at
           this size. */
        white-space: normal;
        width: max-content;
        max-width: 20rem;
        text-align: center;
    }
    .journey-server-grid.is-rows .jmcard-btn[data-tooltip]::before {
        right: auto;
        left: 1.25rem;
        transform: translateY(4px);
    }
    .journey-server-grid.is-rows .jmcard-btn[data-tooltip]:hover::after,
    .journey-server-grid.is-rows .jmcard-btn[data-tooltip]:hover::before {
        transform: translateY(0);
    }
}

/* Below 768px the row has nowhere left to go and the card returns to the
   stacked form it already had, which is the same call the landing band makes
   at its own 640px floor. Nothing to declare: the base .jmcard rules are the
   stacked form, and every rule above is inside a min-width query. */

/* ── Notify-me box on soon / launching cards (2026-09-06) ─────────────────────
   A gold "Notify me" button in the actions column opens a one-line form under the
   row (e-mail + button + one consent sentence). Same dark card palette as the rest
   of the modal; launching cards borrow their blue for the panel's border. */
.jmcard-btn-notify {
    background: linear-gradient(180deg, #D4AF37 0%, #A8891A 100%);
    color: #12100a;
    border-color: rgba(212,175,55,0.6);
    font-weight: 700;
    box-shadow: 0 4px 20px rgba(212,175,55,0.35), inset 0 1px 0 rgba(255,255,220,0.25);
}
.jmcard-btn-notify.is-open { filter: brightness(0.9); box-shadow: inset 0 2px 6px rgba(0,0,0,0.3); }
.jmcard-notify {
    width: 100%;
    margin-top: 0.9rem;
    padding: 0.85rem 1rem 0.8rem;
    border-radius: 0.6rem;
    background: rgba(0,0,0,0.28);
    border: 1px solid rgba(212,175,55,0.28);
    box-shadow: inset 0 1px 0 rgba(212,175,55,0.08);
}
.jmcard--launching .jmcard-notify { border-color: rgba(96,165,250,0.3); }
.jmcard-notify-form { display: flex; flex-direction: column; gap: 0.55rem; }
.jmcard-notify-title {
    font-family: 'Cinzel', serif;
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: rgba(244,226,135,0.9);
}
.jmcard-notify-row { display: flex; gap: 0.5rem; align-items: stretch; }
.jmcard-notify-input {
    flex: 1 1 auto;
    min-width: 0;
    min-height: 2.75rem;
    padding: 0.5rem 0.8rem;
    border-radius: 0.5rem;
    background: rgba(0,0,0,0.5);
    color: #F5ECD5;
    border: 1px solid rgba(212,175,55,0.35);
    font-family: 'Inter', system-ui, sans-serif;
    font-size: 0.9375rem;
    box-shadow: inset 0 2px 4px rgba(0,0,0,0.4);
}
.jmcard-notify-input::placeholder { color: rgba(245,236,213,0.4); }
.jmcard-notify-input:focus { outline: none; border-color: rgba(212,175,55,0.85); box-shadow: 0 0 0 3px rgba(212,175,55,0.2); }
.jmcard-notify-input:disabled { opacity: 0.6; }
.jmcard-notify-btn { flex: 0 0 auto; min-height: 2.75rem; padding: 0.4rem 1.1rem; font-size: 0.8125rem; letter-spacing: 0.1em; }
.jmcard-notify-btn:disabled { cursor: default; filter: saturate(0.6); opacity: 0.75; transform: none; }
.jmcard-notify-msg { margin: 0; font-size: 0.8125rem; line-height: 1.45; color: rgba(239,227,189,0.72); }
.jmcard-notify-msg.is-ok { color: #86EFAC; }
.jmcard-notify-msg.is-error { color: #FCA5A5; }
.jmcard-notify-hp { position: absolute; inset-inline-start: -9999px; width: 1px; height: 1px; opacity: 0; }
/* Row layout: the panel is a third grid row spanning every column. */
.journey-server-grid.is-rows .jmcard-notify { grid-column: 1 / -1; grid-row: 3; margin-top: 0.6rem; }
@media (max-width: 480px) {
    .jmcard-notify-row { flex-direction: column; }
    .jmcard-notify-btn { width: 100%; }
}

/* Roster not announced yet (registry flag `tribes_tba`): the same badge with the
   colour drained out and a dashed edge — "pending", not "5 tribes in grey". The
   hue has to leave with the fact: a violet "?" reads as a value. */
.sd-pill-tribes.is-tba,
.sd-modal-tribes-count.is-tba,
.jmcard-pill-tribes.is-tba,
.swcard-tribes-pill.is-tba {
    background: rgba(232, 217, 176, 0.05);
    border: 1px dashed rgba(212, 175, 55, 0.38);
    color: rgba(232, 217, 176, 0.72);
    cursor: help;
}
