/* Calmhaven guest invitation.
   A guest should not be interrupted by a popup; they should be handed into a room. So the panel opens
   the way the site itself does — aurora, ink, a hairline of light — and the six things a member gets
   are pinned to a constellation that draws itself, because being known here is the actual product.
   The tether stem and the bloom are placed by js/invite.js at the Login chip's own centre, so the art
   points at a real door rather than floating in the middle of the screen.

   Perf notes, deliberate: no backdrop-filter (the site already pays for four permanently blurred
   surfaces), no filter animation, one fixed composited layer, and every looping animation is
   transform/opacity on elements smaller than 40px. Reduced motion, html.rv-off and the measured lite
   tier each get their own honest fallback below. */

.invite{position:fixed;top:0;left:0;z-index:var(--z-invite,1300);width:min(438px,calc(100vw - 20px));
  opacity:0;pointer-events:none;transform:translate3d(0,-12px,0) scale(.955);transform-origin:top right;
  transition:opacity .34s var(--ease-out),transform .72s var(--ease-spring)}
.invite.is-open{opacity:1;transform:none;pointer-events:auto}
.invite.is-closing{opacity:0;transform:translate3d(0,-7px,0) scale(.985);transition:.3s var(--ease-out)}
.invite.is-sheet{width:auto;transform-origin:bottom center;transform:translate3d(0,16px,0) scale(.965)}

.invite-shell{position:relative;overflow:hidden;border-radius:var(--r-xl);padding:1.15rem 1.2rem 1.1rem;
  border:1px solid rgba(196,181,253,.3);background:var(--surface-raised);
  box-shadow:var(--shadow-3),var(--lit-top),0 0 0 1px rgba(11,6,20,.7),0 0 60px -14px rgba(124,58,237,.5);
  max-height:min(76vh,580px);overflow-y:auto;overscroll-behavior:contain}
.invite-shell::-webkit-scrollbar{width:5px}
.invite-shell::-webkit-scrollbar-thumb{background:rgba(167,139,250,.32);border-radius:99px}

/* Curtain: light arriving first, then the panel has depth instead of a flat fill. */
.invite-veil{position:absolute;inset:-30% -20% auto;height:78%;pointer-events:none;
  background:radial-gradient(58% 100% at 50% 0%,rgba(167,139,250,.3),transparent 72%),
    radial-gradient(40% 88% at 86% 2%,rgba(240,171,252,.2),transparent 70%);
  transform:scale(1.2);opacity:0;animation:invite-bloom .85s var(--ease-out) .05s 1 both}
@keyframes invite-bloom{0%{transform:scale(1.22);opacity:0}26%{opacity:1}100%{transform:scale(1);opacity:.92}}
.invite-deep{position:absolute;inset:0;pointer-events:none;opacity:.85;
  background:radial-gradient(130% 52% at 18% -8%,rgba(124,58,237,.2),transparent 62%),
    radial-gradient(96% 44% at 96% 4%,rgba(240,171,252,.1),transparent 60%),
    radial-gradient(120% 70% at 50% 116%,rgba(245,201,123,.05),transparent 64%)}
/* background-position, not a transform: a 438x580 surface on the compositor is the one layer this
   page does not need, and at 26s a position sweep stays under a tenth of a frame per tick. */
.invite-drift{position:absolute;inset:0;pointer-events:none;opacity:.5;background-size:220% 100%;
  background-image:radial-gradient(38% 58% at 12% 22%,rgba(167,139,250,.16),transparent 66%),
    radial-gradient(30% 46% at 68% 64%,rgba(240,171,252,.12),transparent 66%);
  animation:invite-drift 26s linear infinite}
@keyframes invite-drift{0%{background-position:0% 0}100%{background-position:220% 0}}
.invite-star{position:absolute;width:2px;height:2px;border-radius:50%;background:var(--ivory);pointer-events:none;
  opacity:.22;box-shadow:0 0 6px rgba(216,200,255,.6);animation:invite-twinkle 5.5s var(--ease-in-out) infinite}
.invite-star-1{top:17%;left:22%}
.invite-star-2{top:9%;right:26%;animation-delay:-2.1s}
.invite-star-3{bottom:26%;left:31%;animation-delay:-3.4s}
.invite-star-4{bottom:15%;right:16%;animation-delay:-1.2s}
@keyframes invite-twinkle{0%,100%{opacity:.14;transform:scale(1)}50%{opacity:.55;transform:scale(1.6)}}

/* Stem + notch: the panel is attached to the bar, not floating over the page.
   The tether is a child of the root, never of the panel — `.invite-shell` clips its own art, and a line
   that has to leave the card to reach the Login chip would simply be cut off. Its length is the real gap
   that js/invite.js measured, so the light lands on the chip instead of stopping short of it. */
.invite-notch{position:absolute;top:0;left:0;width:100%;height:0;pointer-events:none;transform-origin:top center;
  animation:invite-stem .34s var(--ease-out) .04s 1 both}
.invite-notch::before{content:'';position:absolute;bottom:0;left:var(--invite-stem-x,26px);width:1px;
  height:var(--invite-stem-h,14px);
  background:linear-gradient(180deg,rgba(196,181,253,0),rgba(196,181,253,.78));
  box-shadow:0 0 9px rgba(167,139,250,.7)}
.invite-notch::after{content:'';position:absolute;top:-6px;left:var(--invite-stem-x,26px);margin-left:-5.5px;
  width:11px;height:11px;transform:rotate(45deg);background:#241b40;
  border-left:1px solid rgba(196,181,253,.55);border-top:1px solid rgba(196,181,253,.55)}
.invite.is-sheet .invite-notch{display:none}
@keyframes invite-stem{0%{opacity:0;transform:scaleY(.2)}100%{opacity:1;transform:scaleY(1)}}

/* The one-shot travelling highlight on the top hairline. */
.invite-shell>.invite-edge{position:absolute;top:0;left:0;width:42%;height:1px;pointer-events:none;opacity:0;
  background:linear-gradient(90deg,transparent,rgba(216,200,255,.95),transparent);
  animation:invite-edge 1.8s var(--ease-out) .4s 1 both}
@keyframes invite-edge{0%{opacity:0;transform:translate3d(-105%,0,0)}14%{opacity:1}100%{opacity:0;transform:translate3d(248%,0,0)}}

.invite-head{position:relative;display:flex;align-items:flex-start;justify-content:space-between}
.invite-emblem{position:relative;display:block;width:62px;height:62px;border-radius:50%;flex:0 0 auto;
  transform:scale(.72);opacity:0;animation:invite-emblem .66s var(--ease-spring) .08s 1 both}
@keyframes invite-emblem{0%{transform:scale(.7) rotate(-6deg);opacity:0}100%{transform:none;opacity:1}}
.invite-emblem::before{content:'';position:absolute;inset:2px;border-radius:50%;
  background:radial-gradient(circle at 38% 30%,rgba(196,181,253,.34),rgba(76,42,138,.5) 62%,rgba(11,6,20,.92));
  box-shadow:inset 0 1px 0 rgba(255,255,255,.18),0 8px 22px rgba(4,2,10,.55)}
/* The lit meridian and its spark ride the same rotating box — two gradients, one composited circle. */
.invite-emblem::after{content:'';position:absolute;inset:-7px;border-radius:50%;
  background:conic-gradient(from 0deg,rgba(167,139,250,0) 0deg,rgba(167,139,250,.12) 58deg,rgba(240,171,252,.82) 92deg,
    rgba(216,200,255,.05) 104deg,rgba(167,139,250,0) 200deg);
  -webkit-mask:radial-gradient(closest-side,transparent 96%,#000 97%);
  mask:radial-gradient(closest-side,transparent 96%,#000 97%);
  animation:invite-orbit 9s linear infinite}
@keyframes invite-orbit{to{transform:rotate(360deg)}}
/* The icon sits inside the sphere rather than on top of it, so the aurora gradient stays visible as
   a lit band and the whole emblem reads as one object pulled out of the dark.
   An absolutely positioned <img> with auto width ignores `inset` and falls back to its intrinsic 156px,
   which would hang off-centre out of a 62px dot — so the box is sized, not inferred. */
.invite-emblem img{position:absolute;top:8%;left:8%;width:84%;height:84%;border-radius:50%;object-fit:cover;
  box-shadow:0 0 0 1px rgba(216,200,255,.28),0 4px 12px rgba(4,2,10,.6)}

.invite-x{width:28px;height:28px;margin:-.2rem -.2rem 0 0;padding:6px;border:1px solid transparent;border-radius:9px;
  background:none;color:var(--ivory-4);cursor:pointer;flex:0 0 auto;
  transition:color .25s,background .25s,border-color .25s}
.invite-x:hover,.invite-x:focus-visible{color:var(--aurora);background:rgba(167,139,250,.12);border-color:rgba(167,139,250,.3)}

/* One stroke, one grid, one weight — drawn in the card rather than from an icon font, so six glyphs in
   a column actually look like family instead of six different sizes that happen to be themed. */
.invite-node svg,.invite-x svg{display:block;fill:none;stroke:currentColor;stroke-width:1.7;
  stroke-linecap:round;stroke-linejoin:round}
.invite-node svg{width:22px;height:22px}
/* The close mark lives in a 16px box, so it needs a heavier unit stroke to weigh the same as a 22px
   glyph — the two are meant to read as one family, not as one drawing and its miniature. */
.invite-x svg{width:100%;height:100%;stroke-width:2}

.invite-kicker{position:relative;margin:.95rem 0 0;font-family:var(--font-mono);font-size:.6rem;letter-spacing:.2em;
  text-transform:uppercase;color:var(--aurora-soft);opacity:0;animation:invite-lift .55s var(--ease-out) .2s 1 both}
.invite-title{position:relative;margin:.4rem 0 0;font-family:var(--font-display);font-weight:600;
  font-size:clamp(1.22rem,1rem + 1vw,1.5rem);line-height:1.16;letter-spacing:-.012em;color:var(--ivory);
  font-variation-settings:'opsz' 44;opacity:0;animation:invite-lift .66s var(--ease-out) .28s 1 both}
@keyframes invite-lift{0%{opacity:0;transform:translate3d(0,9px,0)}100%{opacity:1;transform:none}}
.invite-lede{position:relative;margin:.6rem 0 0;color:var(--ivory-2);font-size:.84rem;line-height:1.66;
  opacity:0;animation:invite-lift .6s var(--ease-out) .4s 1 both}

/* The benefits as a constellation: an SVG polyline through six lit points. */
.invite-list{position:relative;list-style:none;margin:1.05rem 0 0;padding:.1rem 0 .05rem;display:grid;gap:.05rem}
.invite-marks{position:absolute;top:0;left:0;overflow:visible;pointer-events:none;z-index:0}
.invite-marks path{fill:none;stroke-linecap:round}
.invite-threads{stroke:rgba(196,181,253,.34);stroke-width:1;stroke-dasharray:1;stroke-dashoffset:1;
  animation:invite-draw 1.25s var(--ease-out) .5s 1 both}
.invite-glow{stroke:rgba(167,139,250,.4);stroke-width:4;stroke-dasharray:1;stroke-dashoffset:1;
  filter:blur(3px);animation:invite-draw 1.45s var(--ease-out) .5s 1 both}
@keyframes invite-draw{to{stroke-dashoffset:0}}
/* The rows are lifted above the artwork on purpose: the svg is appended after them, so a z-index of auto
   would let the line cross over the words. The glyphs and their pool of ink stay above the line as well,
   which is what makes the thread read as passing behind six lights rather than through them. */
.invite-row{position:relative;z-index:1;display:grid;grid-template-columns:34px 1fr;gap:.72rem;align-items:center;padding:.4rem 0}
/* No badge. An outlined disc around a small glyph is what made the column read as six cheap pills, and at
   30px the drawing inside it was a detail rather than an icon. The glyph is 22px now and sits in a soft
   pool of ink that fades to nothing before it could show an edge — which also lets the constellation line
   pass behind it instead of straight through the artwork. */
.invite-node{position:relative;width:34px;height:34px;display:grid;place-items:center;
  color:var(--aurora-soft);
  background:radial-gradient(circle at 50% 48%,rgba(11,6,20,.9) 0 34%,rgba(11,6,20,.45) 55%,transparent 76%);
  opacity:0;transform:scale(.82);animation:invite-ignite .5s var(--ease-spring) calc(.58s + var(--invite-i,0) * .13s) 1 both}
@keyframes invite-ignite{to{opacity:1;transform:none}}
.invite-row strong{display:block;font-size:.82rem;font-weight:600;color:var(--ivory)}
.invite-row span{display:block;margin-top:.14rem;font-size:.745rem;line-height:1.56;color:var(--ivory-3)}
.invite-row{opacity:0;animation:invite-rise .6s var(--ease-out) calc(.6s + var(--invite-i,0) * .13s) 1 both}
@keyframes invite-rise{0%{opacity:0;transform:translate3d(0,8px,0)}100%{opacity:1;transform:none}}
/* Colour only: `both` on the ignite animation keeps holding the glyph's final transform, so a hover
   transform here would be silently ignored rather than being the nudge it looks like. */
.invite-row:hover .invite-node{color:var(--ivory);
  background:radial-gradient(circle at 50% 48%,rgba(11,6,20,.95) 0 34%,rgba(11,6,20,.55) 55%,transparent 76%)}

.invite-foot{position:relative;margin-top:1.1rem;opacity:0;animation:invite-lift .6s var(--ease-out) .52s 1 both}
.invite-cta{position:relative;overflow:hidden;display:flex;align-items:center;justify-content:center;gap:.62rem;width:100%;
  padding:.84rem 1rem;border-radius:var(--r-pill);text-decoration:none;font-weight:700;font-size:.87rem;color:#fff;
  background:linear-gradient(135deg,#5865f2 0%,var(--aurora-deep) 100%);
  border:1px solid rgba(196,181,253,.42);box-shadow:0 10px 26px rgba(88,101,242,.34),var(--lit-top);
  transition:transform .3s var(--ease-out),box-shadow .3s var(--ease-out)}
.invite-cta:hover{transform:translateY(-2px);box-shadow:0 16px 36px rgba(88,101,242,.46)}
.invite-cta:focus-visible{outline:none;box-shadow:var(--focus)}
.invite-cta i{font-size:1rem}
/* The button is a pill, so the highlight has to be clipped by its own radius — and the travel rides the
   compositor as a translate, never as an animated `left`, which would repaint the strip every frame.
   The band crossing the label is the point: it is light moving over the whole button. */
.invite-cta::after{content:'';position:absolute;top:0;left:0;width:42%;height:100%;pointer-events:none;
  background:linear-gradient(100deg,transparent,rgba(255,255,255,.3),transparent);
  transform:translate3d(-110%,0,0);transition:transform .55s var(--ease-out)}
.invite-cta:hover::after{transform:translate3d(260%,0,0)}
.invite-note{display:block;margin-top:.62rem;text-align:center;font-size:.685rem;line-height:1.55;color:var(--ivory-4)}
.invite-note b{font-weight:600;color:var(--ivory-3)}
.invite-quiet{display:block;margin:.68rem auto 0;background:none;border:none;cursor:pointer;
  font-family:var(--font-mono);font-size:.67rem;letter-spacing:.11em;text-transform:uppercase;color:var(--ivory-4);
  transition:color .25s}
.invite-quiet:hover,.invite-quiet:focus-visible{color:var(--aurora)}

/* Narrow: the sheet cannot reach the bar, so the chip itself takes the ring. Three turns, then JS
   drops the class — nothing loops. */
.account-nav.invite-target::after{content:'';position:absolute;inset:-4px;border-radius:99px;pointer-events:none;
  border:1px solid rgba(167,139,250,.55);animation:invite-ring 1.9s var(--ease-out) 3}
@keyframes invite-ring{0%{transform:scale(1);opacity:.9}70%,100%{transform:scale(1.3);opacity:0}}

@media (max-width:420px){
  .invite-shell{padding:1rem .95rem 1rem}
  .invite-emblem{width:54px;height:54px}
  .invite-row{gap:.6rem}
}

/* A guest who asked for stillness gets the composition, minus every movement. */
@media (prefers-reduced-motion:reduce){
  .invite,.invite.is-sheet{transition:opacity .2s linear;transform:none}
  .invite-veil,.invite-edge,.invite-notch,.invite-emblem,.invite-kicker,.invite-title,.invite-lede,
  .invite-foot,.invite-row,.invite-node,.invite-star,.invite-drift,.invite-emblem::after,
  .account-nav.invite-target::after,.invite-threads,.invite-glow{animation:none}
  .invite-veil{opacity:.92;transform:none}
  .invite-emblem,.invite-kicker,.invite-title,.invite-lede,.invite-foot,.invite-row,.invite-node{opacity:1;transform:none}
  .invite-threads,.invite-glow{stroke-dashoffset:0}
  .invite-star{opacity:.3}
  .invite-cta:hover{transform:none}
  .invite-cta::after{display:none}
}
html.rv-off .invite,html.rv-off .invite.is-sheet{transition:opacity .2s linear;transform:none}
html.rv-off .invite-veil,html.rv-off .invite-edge,html.rv-off .invite-notch,html.rv-off .invite-emblem,
html.rv-off .invite-kicker,html.rv-off .invite-title,html.rv-off .invite-lede,html.rv-off .invite-foot,
html.rv-off .invite-row,html.rv-off .invite-node,html.rv-off .invite-star,html.rv-off .invite-drift,
html.rv-off .invite-emblem::after,html.rv-off .account-nav.invite-target::after,
html.rv-off .invite-threads,html.rv-off .invite-glow{animation:none}
html.rv-off .invite-veil{opacity:.92;transform:none}
html.rv-off .invite-emblem,
html.rv-off .invite-kicker,html.rv-off .invite-title,html.rv-off .invite-lede,html.rv-off .invite-foot,
html.rv-off .invite-row,html.rv-off .invite-node{opacity:1;transform:none}
html.rv-off .invite-threads,html.rv-off .invite-glow{stroke-dashoffset:0}
html.rv-off .invite-cta:hover{transform:none}
html.rv-off .invite-cta::after{display:none}
/* lite is chosen by measured frame drops, not by a stated preference, so it keeps the entrance
   choreography and loses only the permanent movement. */
html[data-rv-tier="lite"] .invite-drift,html[data-rv-tier="lite"] .invite-emblem::after,
html[data-rv-tier="lite"] .invite-star{animation:none}
html[data-rv-tier="lite"] .invite-glow{display:none}
