/* stuubzzz.studio · the course page's 3D effects (course-3d.js). Emitted by _src/build.py to /assets/course-3d.<hash>.css
   and linked by /comfyui-course/ only, after site.css, so no other page downloads it.
   Three effects over real HTML that stays the content: the curriculum map + cable rail, the WebGL hero graph and the
   pricing card tilt. Everything that changes what you see is gated on html.js (removed when site.js fails to load),
   no html.no3d (set when this file or course-3d.js fails to load) and prefers-reduced-motion:no-preference, so reduced
   motion, no JS or a failed file show today's page. Prototypes and their notes: _src/lab/course-3d. */

/* ---- 1. curriculum: "fly the workflow". .cg wraps the real .curr list; .cg-off (set by the script) = the plain list.
   The map and the phone bar also carry the hidden attribute, so they stay out of sight if this file never arrives */
.cg-map,.cg-mini{display:none}
@media (prefers-reduced-motion:no-preference){
  /* the rail: one cable per part, lit up to the module you are reading */
  .js:not(.no3d) .cg:not(.cg-off) .mods{position:relative;padding-left:30px}
  .js:not(.no3d) .cg:not(.cg-off) .mods::before,
  .js:not(.no3d) .cg:not(.cg-off) .mods::after{content:"";position:absolute;left:10px;top:0;bottom:0;width:2px;border-radius:2px;pointer-events:none}
  .js:not(.no3d) .cg:not(.cg-off) .mods::before{background:linear-gradient(180deg,var(--line-2),rgba(242,237,227,.06))}
  .js:not(.no3d) .cg:not(.cg-off) .mods::after{
    background:linear-gradient(180deg,color-mix(in srgb,var(--c) 40%,transparent),var(--c));
    box-shadow:0 0 12px color-mix(in srgb,var(--c) 70%,transparent);
    transform-origin:50% 0;transform:scaleY(var(--fill,0));transition:transform .5s var(--ease);
  }
  .js:not(.no3d) .cg:not(.cg-off) .mods summary{position:relative}
  /* the port on the rail, centred on it (rail centre 11px, port 10px wide) */
  .js:not(.no3d) .cg:not(.cg-off) .mods summary::before{
    content:"";position:absolute;left:-24px;top:50%;width:10px;height:10px;margin-top:-5px;border-radius:50%;
    background:var(--ink);border:2px solid rgba(242,237,227,.22);z-index:1;
    transition:background-color .3s,border-color .3s,box-shadow .3s,transform .35s var(--spring);
  }
  .js:not(.no3d) .cg:not(.cg-off) .mods details.is-past > summary::before{border-color:var(--c);background:color-mix(in srgb,var(--c) 55%,var(--ink))}
  .js:not(.no3d) .cg:not(.cg-off) .mods details.is-on > summary::before{border-color:var(--c);background:var(--c);box-shadow:0 0 0 4px color-mix(in srgb,var(--c) 22%,transparent),0 0 16px var(--c);transform:scale(1.12)}
  .js:not(.no3d) .cg:not(.cg-off) .mods details > summary .no{transition:opacity .3s,color .3s}
  .js:not(.no3d) .cg:not(.cg-off) .mods details:not(.is-on):not(.is-past) > summary .no{opacity:.62}
  .js:not(.no3d) .cg:not(.cg-off) .mods details > summary{transition:background-color .35s}
  .js:not(.no3d) .cg:not(.cg-off) .mods details.is-on > summary{background:linear-gradient(90deg,color-mix(in srgb,var(--c) 9%,transparent),transparent 78%)}
  .js:not(.no3d) .cg:not(.cg-off) .mods details.is-on > summary h4{color:var(--bone)}
  /* the free part reads differently from the bundle: a solid cable for Part 1, a dashed "bundle" cable for Parts 2 + 3 */
  .js:not(.no3d) .cg:not(.cg-off) [data-paid] .mods::before{background:repeating-linear-gradient(180deg,rgba(242,237,227,.18) 0 7px,transparent 7px 12px)}
}
/* phones: a narrower rail and a snug number column, so the module titles keep about the room they had and do not
   wrap more than in the plain list (the rail runs through the port centres) */
@media (prefers-reduced-motion:no-preference) and (max-width:560px){
  .js:not(.no3d) .cg:not(.cg-off) .mods{padding-left:18px}
  .js:not(.no3d) .cg:not(.cg-off) .mods::before,
  .js:not(.no3d) .cg:not(.cg-off) .mods::after{left:5px}
  .js:not(.no3d) .cg:not(.cg-off) .mods summary{grid-template-columns:2.1rem 1fr auto;gap:12px}
  .js:not(.no3d) .cg:not(.cg-off) .mods summary::before{left:-17px}
}
/* desktop: the list + a sticky map window beside it */
@media (prefers-reduced-motion:no-preference) and (min-width:1024px){
  .js:not(.no3d) .cg:not(.cg-off){display:grid;grid-template-columns:minmax(0,1.06fr) minmax(0,.94fr);gap:clamp(30px,3.6vw,56px);align-items:start}
  .js:not(.no3d) .cg:not(.cg-off) .cg-map{display:block;position:sticky;top:calc(var(--nav-h) + 22px);order:2}
  .js:not(.no3d) .cg:not(.cg-off) .mods .in{grid-template-columns:1fr;gap:16px;padding-left:calc(3.2rem + 14px)}
}
.cg-map .win{--c:var(--cg-c,var(--volt));transition:box-shadow .6s}
.cg-map .win-bar [data-cg-part]{color:var(--cg-c,var(--volt));transition:color .4s;white-space:nowrap}
.cg-stage{
  position:relative;overflow:hidden;
  /* the whole window, HUD and legend included, stays inside a short viewport while it is sticky */
  height:min(680px,calc(100vh - var(--nav-h) - 96px));min-height:min(460px,calc(100vh - var(--nav-h) - 64px));
  background:
    radial-gradient(120% 60% at 50% -8%,color-mix(in srgb,var(--cg-c,var(--volt)) 16%,transparent),transparent 62%),
    radial-gradient(rgba(242,237,227,.07) 1px,transparent 1.3px) 0 0/26px 26px,
    #0D0D11;
}
.cg-stage canvas{position:absolute;inset:0;width:100%;height:100%;display:block;opacity:0;transition:opacity .7s var(--ease)}
.cg-stage.is-live canvas{opacity:1}
.cg-stage.is-live{background:radial-gradient(120% 60% at 50% -8%,color-mix(in srgb,var(--cg-c,var(--volt)) 16%,transparent),transparent 62%),#0D0D11}
.cg-stage canvas.is-hot{cursor:pointer}
/* top and bottom fades, so the far distance and the HUD sit cleanly in the window */
.cg-stage::before,.cg-stage::after{content:"";position:absolute;left:0;right:0;pointer-events:none;z-index:1}
.cg-stage::before{top:0;height:90px;background:linear-gradient(#0D0D11,rgba(13,13,17,0))}
.cg-stage::after{bottom:0;height:120px;background:linear-gradient(rgba(13,13,17,0),rgba(13,13,17,.94) 70%)}
/* HUD: the course as a queue, 10 + 9 + 9 */
.cg-hud{position:absolute;left:18px;right:18px;bottom:14px;z-index:2;display:flex;flex-wrap:wrap;align-items:center;gap:10px 12px;font:500 .72rem/1 var(--ff-mono);letter-spacing:.06em;color:var(--bone-mute);pointer-events:none}
.cg-q{flex:1;display:flex;gap:4px;height:6px}
.cg-q i{flex:var(--w) 1 0;border-radius:4px;background:rgba(242,237,227,.08);overflow:hidden;position:relative}
.cg-q i::after{content:"";position:absolute;inset:0;background:var(--qc);box-shadow:0 0 10px var(--qc);transform-origin:0 50%;transform:scaleX(var(--f,0));transition:transform .35s var(--ease)}
.cg-n{min-width:7.4em;text-align:right;color:var(--bone-dim);font-variant-numeric:tabular-nums}
.cg-legend{flex:1 1 100%;display:flex;flex-wrap:wrap;gap:8px 16px;font-size:.66rem;letter-spacing:.1em;text-transform:uppercase}
.cg-legend b{font-weight:500;display:inline-flex;align-items:center;gap:7px}
.cg-legend b::before{content:"";width:16px;height:2px;border-radius:2px;background:var(--lc)}
.cg-legend > span{margin-left:auto;color:rgba(242,237,227,.42)}
/* phones and tablets: a slim sticky "queue" bar over the list (the map is desktop-only); anchors such as #part-2 land below it */
@media (prefers-reduced-motion:no-preference) and (max-width:1023.98px){
  .js:not(.no3d) .cg:not(.cg-off) .cg-mini{display:flex;align-items:center;gap:12px;position:sticky;top:var(--nav-h);z-index:5;height:40px;margin-bottom:18px;
    padding:0 2px;font:500 .75rem/1 var(--ff-mono);letter-spacing:.04em;color:var(--bone-dim);
    background:linear-gradient(rgba(10,10,13,.94),rgba(10,10,13,.86));-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);
    border-bottom:1px solid var(--line)}
  .js:not(.no3d) .cg:not(.cg-off) .cg-mini .cg-n{color:var(--cg-c,var(--volt))}
  html.js:not(.no3d):has(.cg:not(.cg-off)){scroll-padding-top:calc(var(--nav-h) + 62px)}
}

/* ---- 2. hero: "living node graph". The SVG window fills a fixed-aspect box as the poster (first paint, and what stays
   without WebGL: a taller ComfyUI canvas, the graph in its middle); the WebGL canvas fades in over it once the page has
   loaded. Phones get the 4:3 box too, so the 3D graph shows all seven nodes */
.h3d-cv{display:none}
@media (prefers-reduced-motion:no-preference){
  .js:not(.no3d) .h3d{position:relative;aspect-ratio:16/11;display:grid}
  .js:not(.no3d) .h3d > .win{display:flex;flex-direction:column;min-height:0;transition:opacity .7s var(--ease),transform .9s var(--ease)}
  .js:not(.no3d) .h3d > .win > .win-body{flex:1;display:grid;align-items:center;background:#121217}
  .js:not(.no3d) .page-course .hero{overflow-x:clip}          /* safety net: the canvas bleed never widens the page */
  .js:not(.no3d) .page-course .hero-art figcaption{position:relative;z-index:1}
  .js:not(.no3d) .h3d-cv{
    display:block;position:absolute;left:-7%;top:-10%;width:122%;height:120%;pointer-events:none;
    opacity:0;transition:opacity 1.1s var(--ease);
    -webkit-mask-image:radial-gradient(ellipse 50% 50% at 50% 50%,#000 82%,transparent 100%);
            mask-image:radial-gradient(ellipse 50% 50% at 50% 50%,#000 82%,transparent 100%);
  }
  .js:not(.no3d) .h3d.is-live .h3d-cv{opacity:1}
  .js:not(.no3d) .h3d.is-live > .win{opacity:0;transform:scale(.965)}
}
/* the canvas bleeds into the right gutter only where there is a gutter to bleed into */
@media (prefers-reduced-motion:no-preference) and (max-width:1279.98px){.js:not(.no3d) .h3d-cv{left:-5%;width:108%}}
@media (prefers-reduced-motion:no-preference) and (max-width:940px){
  .js:not(.no3d) .h3d{aspect-ratio:4/3}
  .js:not(.no3d) .h3d-cv{left:-3%;top:-5%;width:106%;height:110%}
}

/* ---- 3. pricing card tilt: CSS 3D tilt + a glare that follows the pointer (mouse and pen only), and a slow comet of the
   card's accent running round the border. Touch screens get the ring only; reduced motion gets today's still cards */
@property --ang{syntax:"<angle>";inherits:false;initial-value:0deg}
@media (prefers-reduced-motion:no-preference) and (hover:hover) and (pointer:fine){
  .js:not(.no3d) .price-grid{perspective:1500px}
  /* at rest the card keeps the site's transform:none (no 3D layer, text rasterised as usual); the rotation exists only
     while the pointer is on it, and the transition takes it back to flat when it leaves */
  .js:not(.no3d) .plan[data-tilt].rv.in{transition:opacity .8s var(--ease) calc(var(--i,0) * 80ms),transform .6s var(--ease),border-color .25s,box-shadow .5s}
  .js:not(.no3d) .plan[data-tilt].rv.in.is-tilting{transform:rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg));
    transition:opacity .8s var(--ease),transform .12s linear,border-color .25s,box-shadow .5s;transform-style:preserve-3d;
    box-shadow:0 0 0 1px color-mix(in srgb,var(--c) 22%,transparent),0 0 110px -30px color-mix(in srgb,var(--c) 70%,transparent),0 50px 90px -50px rgba(0,0,0,.95)}
  /* layers lift off the card only while it tilts, so the text is never rasterised in 3D at rest */
  .js:not(.no3d) .plan[data-tilt] > :is(.chips,h3,.amount,.cta-row,ul,.fine){transition:transform .6s var(--ease)}
  .js:not(.no3d) .plan[data-tilt].is-tilting > .chips{transform:translateZ(34px)}
  .js:not(.no3d) .plan[data-tilt].is-tilting > h3{transform:translateZ(26px)}
  .js:not(.no3d) .plan[data-tilt].is-tilting > .amount{transform:translateZ(46px)}
  .js:not(.no3d) .plan[data-tilt].is-tilting > .cta-row{transform:translateZ(30px)}
  .js:not(.no3d) .plan[data-tilt].is-tilting > :is(ul,.fine){transform:translateZ(14px)}
  .js:not(.no3d) .plan.is-tilting .glare{opacity:1}
}
.plan .glare{position:absolute;inset:0;border-radius:inherit;pointer-events:none;opacity:0;transition:opacity .45s var(--ease);z-index:3;
  background:
    radial-gradient(520px circle at var(--gx,50%) var(--gy,30%),rgba(255,255,255,.10),rgba(255,255,255,.035) 26%,transparent 58%),
    linear-gradient(calc(var(--gx-deg,0deg) + 115deg),transparent 38%,rgba(255,255,255,.06) 48%,transparent 58%);
  mix-blend-mode:screen}
@media (prefers-reduced-motion:no-preference){
  .js:not(.no3d) .plan[data-tilt]::after{content:"";position:absolute;inset:-1px;border-radius:inherit;padding:1.5px;pointer-events:none;z-index:2;
    background:conic-gradient(from var(--ang),transparent 0deg,transparent 150deg,color-mix(in srgb,var(--c) 55%,transparent) 262deg,#fff 320deg,var(--c) 336deg,transparent 360deg);
    -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;mask-composite:exclude;
    opacity:.55;animation:ring 7s linear infinite}
  .js:not(.no3d) .plan#bundle[data-tilt]::after{opacity:.95;animation-duration:5s}
}
@keyframes ring{to{--ang:360deg}}
@media print{.cg-map,.cg-mini,.h3d-cv,.plan .glare{display:none!important}.js .cg{display:block!important}.h3d > .win{opacity:1!important;transform:none!important}}
