/* ═══════════════════════════════════════════════════════════════
   SOLUM — shell.css: the inner-page shell, "Clean" edition.
   The same design language as the homepage (public/css/home.css), for
   every page in views/ and the server's error page.

   Load order:  site.css → app.css → shell.css → page css (auth/dashboard/…)
                /js/shell.js (classic script) → page fx → page module
   Everything renders without shell.js; the script only adds motion and
   the scrolled/hidden nav states.

   ════════════════════════════ API ════════════════════════════

   TOKENS (site.css :root)
     surfaces   --page #04070E · --band #070B15 · --surface #0B1322 (cards)
                --surface-2 #0F192C · --chip #111A2B · --chip-2 #18243A
     brand      --blue #0070F8 (primary) · --blue-d (hover) · --blue-deep
                --blue-soft rgba(0,112,248,.16) · --cyan #6EC8FF (accents,
                labels, links) · --gloss #ECF6FF
     text       --fg #F0F6FD · --fg-2 #C3CCDA · --mute #94A0B4
                --mute-2 #8B97AB · --faint #4F5B70
     lines      --hair rgba(110,200,255,.10) · --hair-2 (.18)
     status     --ok #7CF2A6 · --warn #FFC85A · --bad #FF6B74
     gradients  --grad-text (gloss→cyan→blue, for the second line of a
                headline) · --grad-big (white→cyan→blue, for big numbers)
     fonts      --f-display Sora (headings; tight negative tracking,
                -.03em … -.055em) · --f-body Geist · --f-mono Geist Mono
                (small uppercase labels) · --f-mark Michroma (ONLY the
                uppercase SOLUM wordmark)
     radii      --r-sm 12 · --r-md 20 · --r-lg 28 · --r-xl 36 · --r-pill 999
     shadows    --sh-1 (resting card) · --sh-2 (raised / hero card, faint
                blue glow) · --sh-3 (hover glow) · --ring (focus ring)
     motion     --ease cubic-bezier(.22,.8,.24,1) · --ease-2 (in-out)
     layout     --wrap 1200px · --gutter clamp(16px,4vw,40px)
                --nav-h 64px (58px < 860px; 102px when the nav has a link row)

   GLOBAL COMPONENTS (site.css + app.css)
     .nav .nav__brand .nav__links   fixed top bar, see site.css
       account pill on the right: a .btn straight in the nav, or a .btn
       inside .nav__account / #navAccount (the slot api.js fills) → gloss
       aria-current="page" link = chip fill. .is-scrolled (JS); .is-hidden
       (JS, small screens only). Under 860px the links form a second row
       that scrolls sideways with edge fades (.is-over/.is-start/.is-end)
     .btn  primary blue pill · --ghost/--soft dark pill · --gloss/--light
           light pill · --danger · --sm · --big/--lg · --wide/--block
           <svg class="btn__arrow"> optional arrow
     .h2 (+<em> gradient) · .lede · .fineprint
     .foot  the homepage footer bar (Terms · Privacy · Discord · ©), on
            every inner page except the auth cards and the error page
     .app  page frame (clears the fixed nav), 1200px wrap like the nav
     .app__head  THE page header on every signed-in page: .sh-eyebrow
           ("Account — Orders") + h1 72px Sora with the second part in
           <em> (data-r="words") + p, anything on the right (.app__who)
           .app__head--grad (whole h1 gradient)
     .tabs  segmented pill control of <button>s, .is-on = active
     .panel  section card, no lift · .idcard (+ __label __big __uuid __note
             __foot __row, --wide, __big.is-owner/.is-none) · .stat (b + span,
             .is-warn/.is-bad) · .keycard (__plan, code, .btn)
     .grid2 .grid3 .idcards   responsive card grids
     .field (> span label, input/select/textarea, > .hint-sm) · .field--check
     .note (--ok --warn --bad; <b> title) · .tbl in .tbl__wrap (th mono)
     .pill (--ok --warn --bad --blue) · .basket .qty .total · .loading .empty
     .auth .auth__box .auth__mark .auth__alt · body.plain .plain__box
     .legal (h1 em, .meta, h2, dl) · mark.todo

   UTILITIES (this file)
     .sh-eyebrow      mono uppercase label with a rotated-square bullet,
                      cyan: <span class="sh-eyebrow">Account — Orders</span>
                      (.sh-eyebrow--c centres it in its line)
     .sh-grad         gloss→cyan→blue gradient text
     .sh-grad-big     white→cyan→blue gradient text for big numbers
     .sh-title        big Sora headline, clamp(40px…76px); <em> inside is
                      gradient. .sh-title--sm for a section-sized one
     .sh-lede         lede paragraph (mute-2, 17–20px, 34em)
     .sh-card         a card: surface, hairline, soft shadow, radius 28,
                      lifts 3px + blue glow on hover. .sh-card--flat: no lift
     .sh-device       rounded bezel for a menu render, as on the homepage:
                      <figure class="sh-device"><div class="sh-device__screen">
                        <div class="sh-device__slot"><img …></div></div></figure>
                      (screen is 4:5; .sh-device__screen--auto lets the
                      image set the height)
     .sh-gem          floating crystal with a still glow:
                      <div class="sh-gem" style="--size:120px">
                        <img src="/assets/logo-256.png" alt=""></div>
                      The wrapper bobs (transform only); the drop-shadow sits
                      on the still <img>.  .sh-gem--still: no bob
     .sh-chip         homepage pill: surface + hairline, blue dot; <b> is fg.
                      Plain mixed inline content works (inline-block):
                      <p class="sh-chip">Exclusive to the <b>Susano</b> executor</p>
     .sh-tag          small mono chip (.sh-tag--blue)
     .sh-icon         46px rounded icon tile, blue stroke svg inside
     .sh-list         <ul> with round blue check bullets (pricing style)
     .sh-hr           hairline divider
     .sh-mono         Geist Mono detail text
     .sh-wrap         1200px centred container with the gutter
     .sh-band         alternate background band (#070B15) with hairlines

   AUTOMATIC (no markup needed)
     background   a soft blue radial glow at the top of the page and a very
                  faint diamond lattice fading out below it (body::before /
                  body::after, static, scrolls away with the page)
     entrance     .app__head .tabs .panel .idcard .stat .keycard .auth__box
                  .tbl__wrap .empty .plain__box and every child of .legal fade
                  and rise 16px on first paint (CSS only, always ends
                  visible). Those below the fold wait for the scroll
                  (.sh-later, JS). Opt out: data-r="none" or .sh-still.
                  Sets of cards in .idcards/.grid2/.grid3/#stats cascade.
     notes        a .note that is un-hidden fades/slides in
     tabs         a blue thumb (.sh-ink) slides under the active tab (JS)
     nav          aria-current on the link for this path, .is-scrolled past
                  8px, .is-hidden while scrolling down, <html class="sh-nav">
                  when the nav has links (JS)

   REVEAL (same contract as before; transform/opacity only, no blur)
     data-r="up" | "fade" | "left" | "zoom" | "words" | "none"
     data-d="3"         stagger slot (×70 ms). Without it, elements that enter
                        the viewport together cascade in DOM order.
     .in                revealed (JS) · .is-ready  stagger delay dropped
     data-r="words"     each word rises out of its own slot (.w > .w__i);
                        <b>/<em> survive the split; words inside <em> keep the
                        gradient (.w__i.is-em)
     data-r-kids="up"   on a container: every child (now and added later) gets
                        data-r="up" with a cascade
     No JS → [data-r] content still appears after ~1.6 s (CSS failsafe).

   HTML CLASSES (set by shell.js on <html>)
     .sh-js  script ran · .sh-fine  mouse/trackpad · .sh-reduced  reduced
     motion · .sh-nav  the nav has a link row

   KEYFRAMES  spin (.loading), sh-rise (entrance, also used by page css),
              sh-bob (.sh-gem), sh-note, sh-safe.

   PERFORMANCE RULES (the owner felt scroll lag before; keep it this way)
     animate transform/opacity only · no backdrop-filter · no full-screen
     moving layers · no infinite background-position loops · no filter on
     anything that moves (put it on a still child) · no :has() on html/body ·
     JS writes a style only when its value changes · overflow-x:clip.
   ═══════════════════════════════════════════════════════════════ */

/* ═══════════════════════════ BACKGROUND ═══════════════════════════
   Static, painted once, scrolls away with the page like the homepage aura. */
body::before{
  content:'';position:absolute;left:0;right:0;top:-200px;z-index:-1;pointer-events:none;
  height:780px;
  background:
    radial-gradient(min(440px,80vw) 330px at 50% 38%,rgba(0,112,248,.20),transparent 70%),
    radial-gradient(min(680px,110vw) 390px at 50% 30%,rgba(110,200,255,.07),transparent 75%)}
body::after{
  content:'';position:absolute;left:0;right:0;top:0;height:720px;z-index:-1;pointer-events:none;
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='72' height='72'%3E%3Cpath d='M36 0 72 36 36 72 0 36Z' fill='none' stroke='%236EC8FF' stroke-opacity='.07' stroke-width='1'/%3E%3C/svg%3E") 50% 0/72px 72px;
  -webkit-mask-image:radial-gradient(60% 70% at 50% 0%,#000 0%,rgba(0,0,0,.35) 45%,transparent 80%);
          mask-image:radial-gradient(60% 70% at 50% 0%,#000 0%,rgba(0,0,0,.35) 45%,transparent 80%)}
body.plain::before{top:auto;bottom:10vh}
body.plain::after{display:none}

/* ═══════════════════════════ TABS THUMB ═══════════════════════════ */
.sh-ink{position:absolute;left:0;top:0;z-index:0;pointer-events:none;
  width:var(--iw,0);height:var(--ih,0);border-radius:var(--r-pill);
  transform:translate(var(--ix,0),var(--iy,0));
  background:var(--blue);
  box-shadow:0 6px 16px -4px rgba(0,112,248,.45),inset 0 1px 0 rgba(255,255,255,.18);
  transition:transform .45s var(--ease),opacity .2s}
.sh-ink.is-cold{transition:none}

/* ═══════════════════════════ NOTES ═══════════════════════════ */
.note:not([hidden]){animation:sh-note .5s var(--ease) backwards}
@keyframes sh-note{from{opacity:0;translate:0 -6px}}

/* ═══════════════════════════ LOADING ═══════════════════════════ */
.loading::before{content:'';display:block;width:18px;height:18px;margin:0 auto 14px;border-radius:50%;
  border:2px solid var(--hair-2);border-top-color:var(--blue);animation:spin .8s linear infinite}

/* ═══════════════════════════ ENTRANCE (automatic) ═══════════════════════════
   CSS only, on first paint. `backwards` fill: the finished state is simply
   the element's own style, so nothing can be stranded. Uses the individual
   `translate` property, leaving `transform` free for hover lifts. */
:is(.app__head,.tabs,.panel,.idcard,.stat,.keycard,.auth__box,.tbl__wrap,.empty,.plain__box,.legal > *):not([data-r]):not(.sh-still){
  animation:sh-rise .8s var(--ease) backwards;
  animation-delay:calc(var(--si,0) * 70ms)}
@keyframes sh-rise{from{opacity:0;translate:0 16px}}
/* below the fold at load — shell.js holds these back and plays them on scroll */
.sh-later:not([data-r]){opacity:0;animation:none!important}
/* nothing inside a revealed block animates twice */
[data-r] :is(.app__head,.tabs,.panel,.idcard,.stat,.keycard,.auth__box,.tbl__wrap,.empty){animation:none}
:is(.idcards,.grid2,.grid3,#stats) > :nth-child(2){--si:1}
:is(.idcards,.grid2,.grid3,#stats) > :nth-child(3){--si:2}
:is(.idcards,.grid2,.grid3,#stats) > :nth-child(4){--si:3}
:is(.idcards,.grid2,.grid3,#stats) > :nth-child(5){--si:4}
:is(.idcards,.grid2,.grid3,#stats) > :nth-child(n+6){--si:5}
.keycard:nth-child(2){--si:1}.keycard:nth-child(3){--si:2}.keycard:nth-child(n+4){--si:3}

/* ═══════════════════════════ REVEAL (data-r) ═══════════════════════════ */
[data-r]{
  transition-property:opacity,translate,scale,transform;
  transition-duration:.9s,1s,1s,.5s;
  transition-timing-function:var(--ease);
  transition-delay:calc(var(--d,0) * 70ms),calc(var(--d,0) * 70ms),calc(var(--d,0) * 70ms),0s}
[data-r="up"]{opacity:0;translate:0 26px}
[data-r="fade"]{opacity:0}
[data-r="left"]{opacity:0;translate:-28px 0}
[data-r="zoom"]{opacity:0;scale:.92}
[data-r].in{opacity:1;translate:none;scale:none}
[data-r].is-ready{transition-delay:0s}
[data-r="none"]{opacity:1;translate:none;scale:none;transition:none}
/* if shell.js never runs, reveal everything anyway */
html:not(.sh-js) [data-r]{animation:sh-safe .5s var(--ease) 1.6s forwards}
@keyframes sh-safe{to{opacity:1;translate:none;scale:none}}

[data-r="words"]{opacity:1;translate:none}
.w{display:inline-block;overflow:hidden;vertical-align:top;padding:.08em .02em .14em;margin:-.08em -.02em -.14em}
.w__i{display:inline-block;transform:translateY(110%);
  transition:transform 1s var(--ease);
  transition-delay:calc(var(--d,0) * 70ms + var(--wi,0) * 50ms)}
[data-r="words"].in .w__i{transform:none}
[data-r="words"].is-ready .w__i{transition-delay:0s}
/* a split <em> hands its gradient to every word, so each keeps the clip */
em.sh-wem{background:none!important;-webkit-background-clip:border-box!important;color:inherit}
.w__i.is-em{font-style:normal;background:var(--grad-text);
  -webkit-background-clip:text;background-clip:text;color:transparent}

/* ═══════════════════════════ UTILITIES ═══════════════════════════ */
.sh-eyebrow{
  display:inline-flex;align-items:center;gap:10px;margin-bottom:16px;
  font:500 12.5px/1 var(--f-mono);letter-spacing:.08em;text-transform:uppercase;color:var(--cyan)}
.sh-eyebrow::before{content:'';flex:none;width:6px;height:6px;border-radius:2px;
  background:var(--blue);transform:rotate(45deg)}
.sh-eyebrow--c{display:flex;justify-content:center}

.sh-grad{background:var(--grad-text);-webkit-background-clip:text;background-clip:text;color:transparent;
  padding-bottom:.08em}
.sh-grad-big{background:var(--grad-big);-webkit-background-clip:text;background-clip:text;color:transparent;padding-bottom:.06em}

.sh-title{margin:0;font:700 clamp(40px,6vw,76px)/1 var(--f-display);letter-spacing:-.05em;color:var(--fg)}
.sh-title--sm{font-size:clamp(30px,3.6vw,44px);letter-spacing:-.045em}
.sh-title em{font-style:normal;background:var(--grad-text);-webkit-background-clip:text;background-clip:text;color:transparent;padding-bottom:.08em}
.sh-lede{max-width:34em;margin:18px 0 0;font-size:clamp(17px,1.5vw,20px);line-height:1.55;color:var(--mute-2)}

.sh-mono{font-family:var(--f-mono);font-weight:500;letter-spacing:.06em}

.sh-wrap{width:100%;max-width:var(--wrap);margin:0 auto;padding-inline:var(--gutter)}
.sh-band{background:var(--band);border-block:1px solid var(--hair)}

.sh-card{position:relative;padding:clamp(22px,2.6vw,32px);border-radius:var(--r-lg);
  background:var(--surface);box-shadow:0 0 0 1px var(--hair),var(--sh-1);
  transition:transform .5s var(--ease)}
.sh-card::after{content:'';position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  box-shadow:0 0 0 1px var(--hair-2),var(--sh-3);opacity:0;transition:opacity .45s var(--ease)}
.sh-card:hover::after{opacity:1}
@media (hover:hover){.sh-card:not(.sh-card--flat):hover{transform:translateY(-3px)}}
.sh-card--flat:hover::after{opacity:0}

/* inline-block, not flex: bare text and a <b> inside stay one run of text
   (a flex gap would split them into separate items) */
.sh-chip{display:inline-block;margin:0;padding:7px 14px 7px 12px;
  border-radius:var(--r-pill);
  background:var(--surface);box-shadow:0 0 0 1px var(--hair),var(--sh-1);
  font:400 14px/1.2 var(--f-body);letter-spacing:0;text-transform:none;color:var(--fg-2)}
.sh-chip b{color:var(--fg);font-weight:600;letter-spacing:0}
.sh-chip::before{content:'';display:inline-block;vertical-align:1px;margin-right:10px;
  width:8px;height:8px;border-radius:50%;background:var(--blue);
  box-shadow:0 0 0 3px rgba(0,112,248,.18)}

.sh-tag{display:inline-flex;align-items:center;height:26px;padding:0 10px;border-radius:var(--r-pill);
  background:var(--chip);font:500 11.5px/1 var(--f-mono);letter-spacing:.04em;text-transform:uppercase;
  color:var(--fg-2);white-space:nowrap}
.sh-tag--blue{background:var(--blue-soft);color:var(--cyan)}

.sh-icon{display:grid;place-items:center;flex:none;width:46px;height:46px;border-radius:14px;background:var(--blue-soft)}
.sh-icon svg{width:24px;height:24px;fill:none;stroke:var(--blue);stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}

:is(ul,ol).sh-list{display:grid;gap:12px;list-style:none;margin:0;padding:0}
.sh-list li{position:relative;padding-left:28px;color:var(--fg-2);font-size:15px;line-height:1.45}
.sh-list li::before{content:'';position:absolute;left:0;top:1px;width:18px;height:18px;border-radius:50%;
  background:var(--blue-soft) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18 18'%3E%3Cpath d='m5.5 9.2 2.3 2.3 4.7-5' fill='none' stroke='%230070F8' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center/18px no-repeat}
.sh-list li b,.sh-list li strong{color:var(--fg)}

.sh-hr{height:1px;border:0;margin:28px 0;background:var(--hair)}

/* device frame (homepage .device) */
.sh-device{position:relative;margin:0;padding:9px;border-radius:var(--r-xl);
  background:linear-gradient(180deg,#16223A 0%,#0B1322 100%);
  box-shadow:0 0 0 1px rgba(110,200,255,.16),inset 0 1px 0 rgba(236,246,255,.08),var(--sh-2)}
.sh-device__screen{position:relative;overflow:hidden;aspect-ratio:4 / 5;border-radius:calc(var(--r-xl) - 9px);
  background:radial-gradient(90% 55% at 50% 0%,rgba(0,112,248,.30),transparent 70%),linear-gradient(180deg,#0B1322 0%,#05080E 100%);
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.04)}
.sh-device__screen--auto{aspect-ratio:auto;padding:7% 8%}
.sh-device__slot{position:absolute;inset:7% 8%;display:flex;align-items:center;justify-content:center}
.sh-device__screen--auto .sh-device__slot{position:static}
.sh-device__slot img{max-width:100%;max-height:100%;width:auto;height:auto;border-radius:10px;
  box-shadow:0 0 0 1px rgba(120,170,255,.12),0 24px 60px -10px rgba(0,0,0,.7)}

/* the gem: the wrapper bobs, the image (with its drop-shadow) stays still */
.sh-gem{--size:96px;position:relative;width:var(--size);aspect-ratio:1;margin-inline:auto;
  display:grid;place-items:center;isolation:isolate;animation:sh-bob 7s ease-in-out infinite alternate}
.sh-gem--still{animation:none}
.sh-gem > img{position:relative;z-index:1;width:100%;height:auto;
  filter:drop-shadow(0 calc(var(--size) * .07) calc(var(--size) * .14) rgba(0,112,248,.45))}
.sh-gem::before{content:'';position:absolute;inset:-30%;z-index:-1;border-radius:50%;pointer-events:none;
  background:radial-gradient(closest-side,rgba(0,112,248,.22),rgba(110,200,255,.06) 55%,transparent)}

/* ═══════════════════════════ KEYFRAMES ═══════════════════════════ */
@keyframes spin{to{transform:rotate(360deg)}}
@keyframes sh-bob{from{transform:translateY(calc(var(--size,96px) * .04))}to{transform:translateY(calc(var(--size,96px) * -.06))}}

/* ═══════════════════════════ REDUCED MOTION ═══════════════════════════ */
@media (prefers-reduced-motion:reduce){
  .sh-gem,.loading::before{animation:none!important}
  :is(.app__head,.tabs,.panel,.idcard,.stat,.keycard,.auth__box,.tbl__wrap,.empty,.plain__box,.legal > *),
  .note:not([hidden]){animation:none!important}
  .sh-later{opacity:1!important}
  [data-r],.w__i{opacity:1!important;translate:none!important;scale:none!important;
    transform:none!important;transition:none!important;animation:none!important}
  .nav,.sh-ink,.btn,.idcard,.stat,.keycard,.sh-card{transition:none}
  .idcard:hover,.stat:hover,.keycard:hover,.sh-card:hover{transform:none}
}
