@flashlearnai/cli 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- :root{--n-0:#ffffff;--n-2:#faf9f8;--n-4:#f4f3f1;--line:#e4e4e8;--line-2:#eeeef2;--sep:#c9ccd4;--ink:#1b1a19;--ink-2:#3b3a39;--muted:#5f5e5c;--faint:#8a8886;--teams:#5b5fc7;--teams-soft:#edecfa;--forest:#0e2417;--emerald:#12965a;--emerald-2:#0c7a48;--mint:#8ff0bd;--azure:#3f63d6;--azure-2:#2b46a0;--grad-dark:linear-gradient(140deg,var(--forest),#132a55);--emerald-soft:#e7f5ec;--emerald-line:#c9e8d6;--ok:#12965a;--ok-soft:#e7f5ec;--bad:#c4314b;--bad-soft:#fdeef1;--avail:#3aa657;--busy:#c50f1f;--away:#f2b73d;--sans:"Segoe UI",system-ui,-apple-system,Roboto,sans-serif;--serif:"Segoe UI",system-ui,-apple-system,Roboto,sans-serif;--mono:ui-monospace,"Cascadia Code",SFMono-Regular,Menlo,monospace;--rk:9px 3px 9px 3px;--ease:cubic-bezier(.22,.8,.28,1);--spring:cubic-bezier(.34,1.42,.5,1);--sh-1:0 1px 2px rgba(0,0,0,.05);--sh-2:0 10px 26px -14px rgba(14,36,23,.32)}*{box-sizing:border-box}html,body,#root{height:100%}body{margin:0;font-family:var(--sans);color:var(--ink);background:radial-gradient(140% 120% at 50% -8%,#dfe1ec,#bfc2d3 55%,#adb0c3);-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}button{font-family:inherit;cursor:pointer;border:none;background:none;color:inherit}img{-webkit-user-drag:none;-webkit-user-select:none;user-select:none}::selection{background:var(--emerald-soft)}.stage{height:100vh;display:grid;place-items:center;padding:26px}.window{width:min(1320px,100%);height:min(816px,calc(100vh - 52px));display:flex;background:var(--n-0);border-radius:16px;overflow:hidden;box-shadow:0 0 0 1px #1820360f,0 2px 6px #18203614,0 36px 70px -30px #0c162899}.rail{width:68px;background:var(--n-2);border-right:1px solid var(--sep);display:flex;flex-direction:column;align-items:stretch;padding-top:8px;gap:2px}.rail-item{display:flex;flex-direction:column;align-items:center;gap:3px;padding:8px 0;color:var(--muted);font-size:9.5px;position:relative;transition:color .15s}.rail-item:hover{color:var(--ink)}.rail-item.on{color:var(--teams)}.rail-item.on:before{content:"";position:absolute;left:0;top:8px;bottom:8px;width:3px;border-radius:0 3px 3px 0;background:var(--teams)}.rail-me{margin-top:auto;margin-bottom:12px;display:grid;place-items:center}.list{width:296px;background:var(--n-0);border-right:1px solid var(--sep);display:flex;flex-direction:column;flex-shrink:0}.list-top{padding:14px 16px 6px;display:flex;align-items:center;justify-content:space-between}.list-top h1{font-size:19px;font-weight:700;margin:0;letter-spacing:-.3px}.list-top .tools{display:flex;gap:12px;color:var(--muted)}.search{margin:2px 12px 8px;background:var(--n-4);border-radius:8px;padding:8px 11px;display:flex;align-items:center;gap:8px;color:var(--faint);font-size:12.5px}.tabs{display:flex;gap:16px;padding:2px 16px 8px;font-size:12.5px;color:var(--muted)}.tabs .on{color:var(--ink);font-weight:600;border-bottom:2px solid var(--teams);padding-bottom:6px}.chat{display:flex;gap:11px;align-items:center;padding:9px 12px;margin:0 6px;border-radius:8px;transition:background .15s}.chat:hover{background:var(--n-4)}.chat.on{background:var(--teams-soft)}.chat .meta{flex:1;min-width:0}.chat .row1{display:flex;justify-content:space-between;align-items:baseline;gap:8px}.chat .name{font-weight:600;font-size:13.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.chat.on .name{color:var(--teams)}.chat .time{font-size:11px;color:var(--faint);flex-shrink:0}.chat .prev{font-size:12px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.chat .prev .you{color:var(--faint)}.chat.unread .prev{color:var(--ink);font-weight:600}.chat.unread .pip{width:7px;height:7px;border-radius:50%;background:var(--teams);flex-shrink:0}.avatar{border-radius:50%;display:flex;align-items:center;justify-content:center;color:#fff;font-weight:700;flex-shrink:0;position:relative;overflow:visible}.avatar .dot{position:absolute;right:-1px;bottom:-1px;width:11px;height:11px;border-radius:50%;border:2px solid #fff}.avatar.bot{background:var(--grad-dark)}.avatar.bot img{border-radius:50%}.conv{flex:1;display:flex;flex-direction:column;min-width:0;background:var(--n-0)}.conv-head{height:58px;border-bottom:1px solid var(--sep);padding:0 18px;display:flex;align-items:center;gap:11px}.conv-head .who{display:flex;flex-direction:column;gap:1px}.conv-head .title-row{display:flex;align-items:center;gap:7px}.conv-head .title{font-weight:700;font-size:15px}.conv-head .sub{font-size:11.5px;color:var(--muted);display:flex;align-items:center;gap:6px}.conv-head .sub .p{width:8px;height:8px;border-radius:50%;background:var(--avail);box-shadow:0 0 0 2px #3aa6572e}.badge{font-size:9px;font-weight:700;color:var(--emerald-2);background:var(--emerald-soft);border:1px solid var(--emerald-line);padding:1px 5px;border-radius:4px;letter-spacing:.4px}.conv-head .actions{margin-left:auto;display:flex;gap:18px;color:var(--muted)}.conv-head .actions svg{transition:color .15s}.conv-head .actions svg:hover{color:var(--ink)}.conv-tabs{height:42px;border-bottom:1px solid var(--sep);padding:0 18px;display:flex;gap:22px;align-items:center;font-size:13px;color:var(--muted)}.conv-tabs .on{color:var(--ink);font-weight:600;height:100%;display:flex;align-items:center;border-bottom:2px solid var(--teams)}.messages{flex:1;overflow-y:auto;padding:22px max(24px,calc((100% - 880px)/2)) 8px;display:flex;flex-direction:column;gap:16px;background:#f4f6f9;scroll-behavior:smooth}.divider{align-self:stretch;display:flex;align-items:center;gap:14px;color:var(--muted);font-size:12px;font-weight:500;margin:2px}.divider:before,.divider:after{content:"";height:1px;background:var(--sep);flex:1}.composer{margin:10px auto 16px;width:min(880px,calc(100% - 36px));border:1px solid var(--line);border-radius:12px;background:#fff;box-shadow:var(--sh-1)}.composer .field{padding:11px 14px;color:var(--faint);font-size:13px}.composer .bar{border-top:1px solid var(--line-2);padding:6px 12px;display:flex;align-items:center;gap:15px;color:var(--muted)}.composer .bar .send{margin-left:auto;color:var(--emerald)}.composer .bar .aa{font-weight:700;font-size:13px}.composer .bar .gif{font-size:11px;font-weight:800;letter-spacing:.5px}.msg{display:flex;gap:11px;animation:rise .5s var(--ease) both}.msg .who{width:32px;height:32px;flex-shrink:0}.msg .body{min-width:0;flex:1}.msg .head{font-size:11.5px;margin-bottom:5px;color:var(--muted)}.msg .head b{color:var(--ink)}.bubble{background:#fff;border:1px solid var(--line-2);border-radius:4px 14px 14px;padding:10px 13px;font-size:13.5px;line-height:1.45;display:inline-block;max-width:80%;box-shadow:var(--sh-1)}.user-msg{align-self:flex-end;max-width:70%;display:flex;flex-direction:column;align-items:flex-end;animation:riseR .45s var(--ease) both}.user-bubble{background:var(--emerald-soft);border:1px solid var(--emerald-line);color:#0c3d25;border-radius:14px 14px 2px;padding:9px 13px;font-size:13px;font-weight:600}.typing{display:inline-flex;gap:4px;padding:12px 14px;background:#fff;border:1px solid var(--line-2);border-radius:4px 14px 14px;box-shadow:var(--sh-1)}.typing i{width:7px;height:7px;border-radius:50%;background:#c7ccc9;animation:blink 1.2s infinite}.typing i:nth-child(2){animation-delay:.2s}.typing i:nth-child(3){animation-delay:.4s}.welcome{max-width:432px;border-radius:28px 10px;overflow:hidden;border:1px solid #143122;background:radial-gradient(125% 112% at 6% 0%,#173d29,#0c2419 55%,#0e2340);color:#fff;box-shadow:var(--sh-2);position:relative;animation:cardIn .55s var(--spring) both}.welcome:after{content:"";position:absolute;top:0;right:0;bottom:0;left:0;background-image:radial-gradient(circle at 1px 1px,rgba(143,240,189,.14) 1px,transparent 0);background-size:18px 18px;opacity:.5;pointer-events:none}.welcome .inner{position:relative;padding:26px 24px 24px}.welcome .mk{width:58px;height:58px;border-radius:18px 6px;overflow:hidden;background:var(--grad-dark);display:grid;place-items:center;box-shadow:0 8px 22px -8px #0009;border:1px solid #204634}.welcome h2{font-family:var(--serif);font-weight:600;font-size:27px;margin:16px 0 6px;letter-spacing:-.4px}.welcome p{color:#bfe9cf;font-size:13.5px;line-height:1.5;margin:0 0 20px;max-width:34ch}.welcome .cta{display:inline-flex;align-items:center;gap:9px;background:var(--mint);color:#0b1c12;font-weight:700;font-size:14px;padding:12px 20px;border-radius:14px 6px;transition:transform .18s var(--spring),box-shadow .18s}.welcome .cta:hover{transform:translateY(-2px);box-shadow:0 12px 26px -12px #8ff0bd99}.welcome .meta{display:flex;gap:18px;margin-top:18px;color:#8db8a1;font-size:11.5px}.welcome .meta b{color:#eafff3;font-family:var(--serif);font-size:15px}.pick{max-width:400px;border-radius:26px 8px;overflow:hidden;border:1px solid var(--line);box-shadow:var(--sh-2);background:#fff;animation:cardIn .5s var(--spring) both}.pick-head{padding:16px 18px 6px;display:flex;align-items:flex-start;justify-content:space-between}.pick-head .psub{display:block;font-size:11.5px;color:var(--faint);margin-top:3px}.pick-head .t{font-family:var(--serif);font-size:19px;font-weight:600;color:var(--forest);letter-spacing:-.3px}.selall{font-size:11px;color:var(--emerald-2);background:var(--emerald-soft);border:1px solid var(--emerald-line);border-radius:10px 4px;padding:5px 11px;font-weight:700;transition:.15s}.selall:hover{background:#d7f0e0}.pick-body{padding:12px 18px 18px}.topics{display:flex;flex-direction:column;gap:9px}.topic{display:flex;align-items:center;gap:12px;border:1.5px solid var(--line);padding:12px 13px;text-align:left;width:100%;background:#fff;transition:transform .16s var(--spring),border-color .15s,background .15s,box-shadow .16s}.topic:nth-child(odd){border-radius:16px 6px}.topic:nth-child(2n){border-radius:6px 16px}.topic:hover{border-color:var(--a);transform:translateY(-1px);box-shadow:0 12px 24px -16px color-mix(in srgb,var(--a) 75%,transparent)}.topic.sel{border-color:var(--a);background:color-mix(in srgb,var(--a) 6%,#fff)}.tglyph{width:36px;height:36px;border-radius:11px 4px;display:grid;place-items:center;color:var(--a);background:color-mix(in srgb,var(--a) 13%,#fff);flex-shrink:0;transition:.16s}.topic.sel .tglyph{background:var(--a);color:#fff}.tmain{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}.tlabel{font-weight:600;font-size:14px;color:var(--ink)}.tmastery{display:flex;align-items:center;gap:8px}.tmastery em{font-style:normal;font-size:10.5px;color:var(--faint)}.tcheck{width:24px;height:24px;border-radius:50%;border:1.6px solid #cdd3cd;display:grid;place-items:center;color:#fff;flex-shrink:0;transition:.16s}.topic.sel .tcheck{background:var(--a);border-color:var(--a);animation:pop .3s var(--spring)}.lone{margin:0 0 4px;font-size:15px;line-height:1.5;color:var(--ink);background:var(--emerald-soft);border:1px solid var(--emerald-line);border-radius:12px 4px;padding:12px 14px}.welcome .cmd{margin:14px 0 0;font-family:var(--mono);font-size:12.5px;color:var(--mint);background:#00000047;border:1px solid rgba(143,240,189,.22);border-radius:8px 3px;padding:9px 12px;display:inline-block}.capped{margin:9px 2px 0;font-size:11px;line-height:1.5;color:var(--faint);text-align:center}.start{width:100%;margin-top:15px;background:var(--grad-dark);color:#fff;border:1.5px solid transparent;border-radius:16px 6px;padding:13px;font-weight:600;font-size:14px;display:flex;align-items:center;justify-content:center;gap:8px;transition:transform .18s var(--spring),box-shadow .18s,background .2s,color .2s,border-color .2s}.start:not(:disabled):hover{transform:translateY(-2px);box-shadow:0 14px 30px -14px #1f4fb08c}.start:disabled{background:transparent;color:var(--faint);border-color:var(--line);cursor:default}.qcard{position:relative;max-width:432px;padding:20px 20px 18px;background:linear-gradient(180deg,#fff,#fcfdfb);border:1px solid var(--line);border-radius:28px 8px;box-shadow:var(--sh-2),inset 0 1px #fff;overflow:hidden;animation:cardIn .5s var(--spring) both}.qtop{position:relative;display:flex;align-items:center;justify-content:space-between}.qbrand{display:flex;align-items:center;gap:8px;font-weight:600;font-size:12px;color:var(--forest)}.qbrand .m{width:22px;height:22px;border-radius:8px 3px;overflow:hidden;background:var(--grad-dark);display:grid;place-items:center}.qmeta{position:relative;display:flex;align-items:center;gap:11px;margin:15px 0 3px}.qmeta .frac{font-family:var(--mono);font-size:11px;color:var(--faint);letter-spacing:.5px;flex-shrink:0}.qseg{flex:1;display:flex;gap:3px}.qseg>i{flex:1;height:5px;border-radius:2px;background:#e6ebe6;transition:background .4s}.qseg>i.done{background:var(--emerald)}.qseg>i.cur{background:var(--azure)}.qtext{position:relative;font-family:var(--serif);font-weight:700;font-size:22px;line-height:1.24;color:var(--forest);letter-spacing:-.4px;margin:12px 0 16px}.qchoices{position:relative;display:flex;flex-direction:column;gap:9px}.qchoice{display:flex;align-items:center;gap:13px;padding:13px 15px;border:1.5px solid var(--line);background:#fff;text-align:left;width:100%;font-size:14.5px;color:var(--ink);transition:transform .18s var(--spring),border-color .16s,box-shadow .16s,background .16s;animation:choiceIn .42s var(--spring) both}.qc-text{flex:1}.qc-mark{width:22px;height:22px;border-radius:50%;display:grid;place-items:center;color:#fff;flex-shrink:0;animation:pop .3s var(--spring)}.qc-mark.ok{background:var(--ok)}.qc-mark.no{background:var(--bad)}.qchoice:nth-child(odd){border-radius:16px 6px}.qchoice:nth-child(2n){border-radius:6px 16px}.qchoice:nth-child(1){animation-delay:.06s}.qchoice:nth-child(2){animation-delay:.13s}.qchoice:nth-child(3){animation-delay:.2s}.qchoice:nth-child(4){animation-delay:.27s}.qchoice:not(:disabled):hover{border-color:var(--emerald);transform:translateY(-2px);box-shadow:0 12px 24px -14px #12965a99}.qchoice:disabled{cursor:default}.qchoice .k{width:27px;height:27px;border-radius:var(--rk);background:var(--n-4);color:var(--emerald-2);font-weight:800;font-size:12px;display:grid;place-items:center;flex-shrink:0;border:1px solid var(--line);transition:.16s}.qchoice:not(:disabled):hover .k{background:var(--forest);color:var(--mint)}.qchoice.correct{border-color:var(--ok);background:var(--ok-soft);font-weight:600}.qchoice.correct .k{background:var(--ok);color:#fff;animation:pop .34s var(--spring)}.qchoice.wrong{border-color:var(--bad);background:var(--bad-soft);font-weight:600}.qchoice.wrong .k{background:var(--bad);color:#fff}.qchoice.dim{opacity:.4}.confused{margin:14px 0 0;font-size:12.5px;line-height:1.55;color:var(--muted)}.confused b{color:var(--ink)}.qsrc{position:relative;display:flex;align-items:center;gap:7px;width:100%;font-size:10.5px;color:var(--faint);margin:16px 0 0;padding:12px 0 0;border-top:1px dashed var(--line);font-family:var(--mono);background:none;text-align:left;transition:color .15s}.qsrc:hover{color:var(--muted)}.qsrc .chev{margin-left:auto;transition:transform .22s var(--ease)}.qsrc.open .chev{transform:rotate(180deg)}.qcode{margin-top:11px;border-radius:12px 4px;overflow:hidden;border:1px solid #17301f;box-shadow:0 12px 26px -18px #0e2417a6;animation:codeIn .3s var(--ease) both}.qcode-h{background:#0b1c12;color:#9cc7b0;padding:8px 12px;font-family:var(--mono);font-size:10px;display:flex;align-items:center;gap:7px;border-bottom:1px solid #17301f;letter-spacing:.2px}.qcode-h .g{color:var(--mint)}.qcode pre{margin:0;padding:12px 14px;background:#0e2417;color:#cfe9db;font-family:var(--mono);font-size:11.5px;line-height:1.6;white-space:pre-wrap;word-break:break-word}@keyframes codeIn{0%{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:none}}.qsrc .sha{color:var(--emerald-2);background:var(--emerald-soft);border-radius:5px;padding:1px 6px}.grade{margin-top:15px}.grade-q{font-size:11.5px;color:var(--muted);font-weight:600;letter-spacing:.2px;margin-bottom:9px}.grade-btns{display:grid;grid-template-columns:1fr 1fr;gap:7px}.g-primary{width:100%;margin-top:8px;display:flex;align-items:center;justify-content:center;gap:8px;padding:12px;border:none;border-radius:14px 6px;background:var(--grad-dark);color:#fff;font-weight:600;font-size:13.5px;transition:transform .15s var(--spring),box-shadow .18s}.g-primary:hover{transform:translateY(-1px);box-shadow:0 12px 26px -14px #122a5280}.g{display:flex;flex-direction:column;align-items:center;gap:3px;padding:10px 4px;border:1.5px solid var(--line);border-radius:12px 4px;font-size:12.5px;font-weight:700;background:#fff;color:var(--ink);transition:transform .15s var(--spring),border-color .15s,box-shadow .15s,background .15s}.g em{font-style:normal;font-size:9.5px;color:var(--faint);font-weight:600}.g:hover{transform:translateY(-2px);box-shadow:var(--sh-1)}.g-hard:hover{border-color:#c78a1f;background:#fdf3e1;color:#a3701a}.g-easy:hover{border-color:var(--azure);background:#eef2fd;color:var(--azure-2)}.graded{margin-top:13px;font-size:12px;color:var(--muted)}.graded.unrecorded{color:var(--bad)}.qtext,.lone,.qsrc{overflow-wrap:anywhere}.results{position:relative;max-width:420px;border-radius:28px 8px;overflow:hidden;border:1px solid var(--line);box-shadow:var(--sh-2);background:linear-gradient(180deg,#fff,#fbfdfb);padding:22px;text-align:left;animation:cardIn .5s var(--spring) both}.results-top{display:flex;align-items:center;gap:16px}.results .ring .val{font-family:var(--serif);font-size:26px;font-weight:700;color:var(--forest)}.results-head h3{font-family:var(--serif);font-size:19px;color:var(--forest);margin:0 0 4px}.results-head .sub{color:var(--muted);font-size:12.5px;line-height:1.5}.missed{display:flex;flex-direction:column;gap:8px}.miss{display:flex;align-items:center;gap:10px;font-size:12.5px;color:var(--ink-2)}.miss .mx{width:20px;height:20px;flex-shrink:0;border-radius:50%;background:var(--bad-soft);color:var(--bad);display:grid;place-items:center}.miss .mq{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.results .again{width:100%;margin-top:18px}.confetti{position:absolute;top:0;left:0;right:0;height:0;pointer-events:none}.confetti i{position:absolute;top:0;width:8px;height:12px;border-radius:2px;opacity:0;animation:fall 1.4s var(--ease) forwards}.details{width:300px;border-left:1px solid var(--sep);background:var(--n-2);display:flex;flex-direction:column;flex-shrink:0;overflow-y:auto}.details .hero{padding:24px 20px 20px;text-align:center;border-bottom:1px solid var(--line)}.details .hero .mk{width:66px;height:66px;border-radius:20px 7px;overflow:hidden;margin:0 auto 13px;background:var(--grad-dark);display:grid;place-items:center;box-shadow:var(--sh-2)}.details .hero h3{font-family:var(--serif);font-size:20px;color:var(--forest);margin:0 0 2px}.details .hero .by{font-size:12px;color:var(--muted)}.details .hero .pill{display:inline-flex;align-items:center;gap:6px;margin-top:12px;font-size:11.5px;color:var(--emerald-2);background:var(--emerald-soft);border:1px solid var(--emerald-line);border-radius:99px;padding:4px 11px;font-weight:600}.details .hero .pill .p{width:7px;height:7px;border-radius:50%;background:var(--avail)}.dsec{padding:16px 20px;border-bottom:1px solid var(--line)}.dsec h4{font-size:10.5px;letter-spacing:1.2px;text-transform:uppercase;color:var(--faint);margin:0 0 10px;font-weight:700}.dsec p{margin:0;font-size:12.5px;line-height:1.55;color:var(--ink-2)}.steps{display:flex;flex-direction:column;gap:11px}.step{display:flex;gap:11px;align-items:flex-start;font-size:12.5px;color:var(--ink-2)}.step .n{width:22px;height:22px;flex-shrink:0;border-radius:7px 3px;background:var(--grad-dark);color:var(--mint);font-weight:700;font-size:11px;display:grid;place-items:center}.dstats{display:flex;gap:10px}.dstat{flex:1;border:1px solid var(--line);border-radius:12px 5px;padding:11px 8px;text-align:center;background:#fff}.dstat b{display:block;font-family:var(--serif);font-size:21px;color:var(--forest);line-height:1}.dstat span{font-size:10px;color:var(--faint);letter-spacing:.3px;text-transform:uppercase}.prog{display:flex;align-items:center;gap:14px;margin-bottom:14px}.ring{position:relative;flex-shrink:0;display:grid;place-items:center}.ring .rval{font-family:var(--serif);font-size:17px;color:var(--forest);letter-spacing:-.3px}.pline{margin:0;font-size:12.5px;line-height:1.5;color:var(--muted)}.pline b{color:var(--ink);font-size:14px}.runs{display:flex;flex-direction:column;gap:8px}.run{display:flex;align-items:center;gap:10px;font-size:12.5px;color:var(--ink-2)}.run .lab{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.run.now .lab{font-weight:700;color:var(--ink)}.run.next{opacity:.45}.run .sc{font-family:var(--mono);font-size:10.5px;color:var(--faint)}.run .bars{display:flex;gap:2px}.run .bars i{width:12px;height:5px;border-radius:2px;background:#e0e5e0;transition:background .3s}.run .bars i.ok{background:var(--emerald)}.run .bars i.no{background:var(--bad)}.insights{display:flex;flex-direction:column;gap:12px}.insight{display:flex;flex-direction:column;gap:5px}.insight-head{display:flex;align-items:baseline;justify-content:space-between;gap:10px;font-size:12.5px;color:var(--ink-2)}.insight-head span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.insight-head b{font-family:var(--mono);font-size:11px;color:var(--forest)}.insight-track{display:block;height:6px;border-radius:3px;background:#e1e6e2;overflow:hidden}.insight-track i{display:block;height:100%;border-radius:inherit;background:var(--emerald)}.insight small{font-size:10.5px;color:var(--faint)}@keyframes rise{0%{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}@keyframes riseR{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}@keyframes cardIn{0%{opacity:0;transform:translateY(16px) scale(.985)}to{opacity:1;transform:none}}@keyframes choiceIn{0%{opacity:0;transform:translate(-10px)}to{opacity:1;transform:none}}@keyframes pop{0%{transform:scale(.5)}60%{transform:scale(1.18)}to{transform:scale(1)}}@keyframes blink{0%,60%,to{opacity:.3;transform:translateY(0)}30%{opacity:1;transform:translateY(-3px)}}@keyframes fall{0%{opacity:1;transform:translateY(0) rotate(0)}to{opacity:0;transform:translateY(320px) rotate(320deg)}}@media(prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}.live-insights{display:none}@media(max-width:1120px){.details{display:none}.live-insights{display:block;background:var(--n-2);border:1px solid var(--sep);border-radius:8px;margin-bottom:16px}.live-insights summary{cursor:pointer;padding:12px 20px;color:var(--forest)}}@media(max-width:840px){.rail{display:none}.list{width:86px}.list .name,.list .prev,.list-top h1,.search,.tabs,.chat .time{display:none}.chat{justify-content:center;padding:9px 0;margin:0}.window{height:calc(100vh - 24px)}.stage{padding:12px}.messages{padding:18px 14px 8px}}
1
+ :root{--n-0:#ffffff;--n-2:#faf9f8;--n-4:#f4f3f1;--line:#e4e4e8;--line-2:#eeeef2;--sep:#c9ccd4;--ink:#1b1a19;--ink-2:#3b3a39;--muted:#5f5e5c;--faint:#8a8886;--teams:#5b5fc7;--teams-soft:#edecfa;--forest:#0e2417;--emerald:#12965a;--emerald-2:#0c7a48;--mint:#8ff0bd;--azure:#3f63d6;--azure-2:#2b46a0;--grad-dark:linear-gradient(140deg,var(--forest),#132a55);--emerald-soft:#e7f5ec;--emerald-line:#c9e8d6;--ok:#12965a;--ok-soft:#e7f5ec;--bad:#c4314b;--bad-soft:#fdeef1;--avail:#3aa657;--busy:#c50f1f;--away:#f2b73d;--sans:"Segoe UI",system-ui,-apple-system,Roboto,sans-serif;--serif:"Segoe UI",system-ui,-apple-system,Roboto,sans-serif;--mono:ui-monospace,"Cascadia Code",SFMono-Regular,Menlo,monospace;--rk:9px 3px 9px 3px;--ease:cubic-bezier(.22,.8,.28,1);--spring:cubic-bezier(.34,1.42,.5,1);--sh-1:0 1px 2px rgba(0,0,0,.05);--sh-2:0 10px 26px -14px rgba(14,36,23,.32)}*{box-sizing:border-box}html,body,#root{height:100%}body{margin:0;font-family:var(--sans);color:var(--ink);background:radial-gradient(140% 120% at 50% -8%,#dfe1ec,#bfc2d3 55%,#adb0c3);-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}button{font-family:inherit;cursor:pointer;border:none;background:none;color:inherit}img{-webkit-user-drag:none;-webkit-user-select:none;user-select:none}::selection{background:var(--emerald-soft)}.stage{height:100vh;display:grid;place-items:center;padding:26px}.window{width:min(1320px,100%);height:min(816px,calc(100vh - 52px));display:flex;background:var(--n-0);border-radius:16px;overflow:hidden;box-shadow:0 0 0 1px #1820360f,0 2px 6px #18203614,0 36px 70px -30px #0c162899}.rail{width:68px;background:var(--n-2);border-right:1px solid var(--sep);display:flex;flex-direction:column;align-items:stretch;padding-top:8px;gap:2px}.rail-item{display:flex;flex-direction:column;align-items:center;gap:3px;padding:8px 0;color:var(--muted);font-size:9.5px;position:relative;transition:color .15s}.rail-item:hover{color:var(--ink)}.rail-item.on{color:var(--teams)}.rail-item.on:before{content:"";position:absolute;left:0;top:8px;bottom:8px;width:3px;border-radius:0 3px 3px 0;background:var(--teams)}.rail-me{margin-top:auto;margin-bottom:12px;display:grid;place-items:center}.list{width:296px;background:var(--n-0);border-right:1px solid var(--sep);display:flex;flex-direction:column;flex-shrink:0}.list-top{padding:14px 16px 6px;display:flex;align-items:center;justify-content:space-between}.list-top h1{font-size:19px;font-weight:700;margin:0;letter-spacing:-.3px}.list-top .tools{display:flex;gap:12px;color:var(--muted)}.search{margin:2px 12px 8px;background:var(--n-4);border-radius:8px;padding:8px 11px;display:flex;align-items:center;gap:8px;color:var(--faint);font-size:12.5px}.tabs{display:flex;gap:16px;padding:2px 16px 8px;font-size:12.5px;color:var(--muted)}.tabs .on{color:var(--ink);font-weight:600;border-bottom:2px solid var(--teams);padding-bottom:6px}.chat{display:flex;gap:11px;align-items:center;padding:9px 12px;margin:0 6px;border-radius:8px;transition:background .15s}.chat:hover{background:var(--n-4)}.chat.on{background:var(--teams-soft)}.chat .meta{flex:1;min-width:0}.chat .row1{display:flex;justify-content:space-between;align-items:baseline;gap:8px}.chat .name{font-weight:600;font-size:13.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.chat.on .name{color:var(--teams)}.chat .time{font-size:11px;color:var(--faint);flex-shrink:0}.chat .prev{font-size:12px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.chat .prev .you{color:var(--faint)}.chat.unread .prev{color:var(--ink);font-weight:600}.chat.unread .pip{width:7px;height:7px;border-radius:50%;background:var(--teams);flex-shrink:0}.avatar{border-radius:50%;display:flex;align-items:center;justify-content:center;color:#fff;font-weight:700;flex-shrink:0;position:relative;overflow:visible}.avatar .dot{position:absolute;right:-1px;bottom:-1px;width:11px;height:11px;border-radius:50%;border:2px solid #fff}.avatar.bot{background:var(--grad-dark)}.avatar.bot img{border-radius:50%}.conv{flex:1;display:flex;flex-direction:column;min-width:0;background:var(--n-0)}.conv-head{height:58px;border-bottom:1px solid var(--sep);padding:0 18px;display:flex;align-items:center;gap:11px}.conv-head .who{display:flex;flex-direction:column;gap:1px}.conv-head .title-row{display:flex;align-items:center;gap:7px}.conv-head .title{font-weight:700;font-size:15px}.conv-head .sub{font-size:11.5px;color:var(--muted);display:flex;align-items:center;gap:6px}.conv-head .sub .p{width:8px;height:8px;border-radius:50%;background:var(--avail);box-shadow:0 0 0 2px #3aa6572e}.badge{font-size:9px;font-weight:700;color:var(--emerald-2);background:var(--emerald-soft);border:1px solid var(--emerald-line);padding:1px 5px;border-radius:4px;letter-spacing:.4px}.conv-head .actions{margin-left:auto;display:flex;gap:18px;color:var(--muted)}.conv-head .actions svg{transition:color .15s}.conv-head .actions svg:hover{color:var(--ink)}.conv-tabs{height:42px;border-bottom:1px solid var(--sep);padding:0 18px;display:flex;gap:22px;align-items:center;font-size:13px;color:var(--muted)}.conv-tabs .on{color:var(--ink);font-weight:600;height:100%;display:flex;align-items:center;border-bottom:2px solid var(--teams)}.messages{flex:1;overflow-y:auto;padding:22px max(24px,calc((100% - 880px)/2)) 8px;display:flex;flex-direction:column;gap:16px;background:#f4f6f9;scroll-behavior:smooth}.divider{align-self:stretch;display:flex;align-items:center;gap:14px;color:var(--muted);font-size:12px;font-weight:500;margin:2px}.divider:before,.divider:after{content:"";height:1px;background:var(--sep);flex:1}.composer{margin:10px auto 16px;width:min(880px,calc(100% - 36px));border:1px solid var(--line);border-radius:12px;background:#fff;box-shadow:var(--sh-1)}.composer .field{padding:11px 14px;color:var(--faint);font-size:13px}.composer .bar{border-top:1px solid var(--line-2);padding:6px 12px;display:flex;align-items:center;gap:15px;color:var(--muted)}.composer .bar .send{margin-left:auto;color:var(--emerald)}.composer .bar .aa{font-weight:700;font-size:13px}.composer .bar .gif{font-size:11px;font-weight:800;letter-spacing:.5px}.msg{display:flex;gap:11px;animation:rise .5s var(--ease) both}.msg .who{width:32px;height:32px;flex-shrink:0}.msg .body{min-width:0;flex:1}.msg .head{font-size:11.5px;margin-bottom:5px;color:var(--muted)}.msg .head b{color:var(--ink)}.bubble{background:#fff;border:1px solid var(--line-2);border-radius:4px 14px 14px;padding:10px 13px;font-size:13.5px;line-height:1.45;display:inline-block;max-width:80%;box-shadow:var(--sh-1)}.user-msg{align-self:flex-end;max-width:70%;display:flex;flex-direction:column;align-items:flex-end;animation:riseR .45s var(--ease) both}.user-bubble{background:var(--emerald-soft);border:1px solid var(--emerald-line);color:#0c3d25;border-radius:14px 14px 2px;padding:9px 13px;font-size:13px;font-weight:600}.typing{display:inline-flex;gap:4px;padding:12px 14px;background:#fff;border:1px solid var(--line-2);border-radius:4px 14px 14px;box-shadow:var(--sh-1)}.typing i{width:7px;height:7px;border-radius:50%;background:#c7ccc9;animation:blink 1.2s infinite}.typing i:nth-child(2){animation-delay:.2s}.typing i:nth-child(3){animation-delay:.4s}.welcome{max-width:432px;border-radius:28px 10px;overflow:hidden;border:1px solid #143122;background:radial-gradient(125% 112% at 6% 0%,#173d29,#0c2419 55%,#0e2340);color:#fff;box-shadow:var(--sh-2);position:relative;animation:cardIn .55s var(--spring) both}.welcome:after{content:"";position:absolute;top:0;right:0;bottom:0;left:0;background-image:radial-gradient(circle at 1px 1px,rgba(143,240,189,.14) 1px,transparent 0);background-size:18px 18px;opacity:.5;pointer-events:none}.welcome .inner{position:relative;padding:26px 24px 24px}.welcome .mk{width:58px;height:58px;border-radius:18px 6px;overflow:hidden;background:var(--grad-dark);display:grid;place-items:center;box-shadow:0 8px 22px -8px #0009;border:1px solid #204634}.welcome h2{font-family:var(--serif);font-weight:600;font-size:27px;margin:16px 0 6px;letter-spacing:-.4px}.welcome p{color:#bfe9cf;font-size:13.5px;line-height:1.5;margin:0 0 20px;max-width:34ch}.welcome .cta{display:inline-flex;align-items:center;gap:9px;background:var(--mint);color:#0b1c12;font-weight:700;font-size:14px;padding:12px 20px;border-radius:14px 6px;transition:transform .18s var(--spring),box-shadow .18s}.welcome .cta:hover{transform:translateY(-2px);box-shadow:0 12px 26px -12px #8ff0bd99}.welcome .meta{display:flex;gap:18px;margin-top:18px;color:#8db8a1;font-size:11.5px}.welcome .meta b{color:#eafff3;font-family:var(--serif);font-size:15px}.pick{max-width:400px;border-radius:26px 8px;overflow:hidden;border:1px solid var(--line);box-shadow:var(--sh-2);background:#fff;animation:cardIn .5s var(--spring) both}.pick-head{padding:16px 18px 6px;display:flex;align-items:flex-start;justify-content:space-between}.pick-head .psub{display:block;font-size:11.5px;color:var(--faint);margin-top:3px}.pick-head .t{font-family:var(--serif);font-size:19px;font-weight:600;color:var(--forest);letter-spacing:-.3px}.selall{font-size:11px;color:var(--emerald-2);background:var(--emerald-soft);border:1px solid var(--emerald-line);border-radius:10px 4px;padding:5px 11px;font-weight:700;transition:.15s}.selall:hover{background:#d7f0e0}.pick-body{padding:12px 18px 18px}.topics{display:flex;flex-direction:column;gap:9px}.topic{display:flex;align-items:center;gap:12px;border:1.5px solid var(--line);padding:12px 13px;text-align:left;width:100%;background:#fff;transition:transform .16s var(--spring),border-color .15s,background .15s,box-shadow .16s}.topic:nth-child(odd){border-radius:16px 6px}.topic:nth-child(2n){border-radius:6px 16px}.topic:hover{border-color:var(--a);transform:translateY(-1px);box-shadow:0 12px 24px -16px color-mix(in srgb,var(--a) 75%,transparent)}.topic.sel{border-color:var(--a);background:color-mix(in srgb,var(--a) 6%,#fff)}.tglyph{width:36px;height:36px;border-radius:11px 4px;display:grid;place-items:center;color:var(--a);background:color-mix(in srgb,var(--a) 13%,#fff);flex-shrink:0;transition:.16s}.topic.sel .tglyph{background:var(--a);color:#fff}.tmain{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}.tlabel{font-weight:600;font-size:14px;color:var(--ink)}.tmastery{display:flex;align-items:center;gap:8px}.tmastery em{font-style:normal;font-size:10.5px;color:var(--faint)}.tcheck{width:24px;height:24px;border-radius:50%;border:1.6px solid #cdd3cd;display:grid;place-items:center;color:#fff;flex-shrink:0;transition:.16s}.topic.sel .tcheck{background:var(--a);border-color:var(--a);animation:pop .3s var(--spring)}.lone{margin:0 0 4px;font-size:15px;line-height:1.5;color:var(--ink);background:var(--emerald-soft);border:1px solid var(--emerald-line);border-radius:12px 4px;padding:12px 14px}.welcome .cmd{margin:14px 0 0;font-family:var(--mono);font-size:12.5px;color:var(--mint);background:#00000047;border:1px solid rgba(143,240,189,.22);border-radius:8px 3px;padding:9px 12px;display:inline-block}.capped{margin:9px 2px 0;font-size:11px;line-height:1.5;color:var(--faint);text-align:center}.start{width:100%;margin-top:15px;background:var(--grad-dark);color:#fff;border:1.5px solid transparent;border-radius:16px 6px;padding:13px;font-weight:600;font-size:14px;display:flex;align-items:center;justify-content:center;gap:8px;transition:transform .18s var(--spring),box-shadow .18s,background .2s,color .2s,border-color .2s}.start:not(:disabled):hover{transform:translateY(-2px);box-shadow:0 14px 30px -14px #1f4fb08c}.start:disabled{background:transparent;color:var(--faint);border-color:var(--line);cursor:default}.qcard{position:relative;max-width:432px;padding:20px 20px 18px;background:linear-gradient(180deg,#fff,#fcfdfb);border:1px solid var(--line);border-radius:28px 8px;box-shadow:var(--sh-2),inset 0 1px #fff;overflow:hidden;animation:cardIn .5s var(--spring) both}.qtop{position:relative;display:flex;align-items:center;justify-content:space-between}.qbrand{display:flex;align-items:center;gap:8px;font-weight:600;font-size:12px;color:var(--forest)}.qbrand .m{width:22px;height:22px;border-radius:8px 3px;overflow:hidden;background:var(--grad-dark);display:grid;place-items:center}.qmeta{position:relative;display:flex;align-items:center;gap:11px;margin:15px 0 3px}.qmeta .frac{font-family:var(--mono);font-size:11px;color:var(--faint);letter-spacing:.5px;flex-shrink:0}.qseg{flex:1;display:flex;gap:3px}.qseg>i{flex:1;height:5px;border-radius:2px;background:#e6ebe6;transition:background .4s}.qseg>i.done{background:var(--emerald)}.qseg>i.cur{background:var(--azure)}.qtext{position:relative;font-family:var(--serif);font-weight:700;font-size:22px;line-height:1.24;color:var(--forest);letter-spacing:-.4px;margin:12px 0 16px}.qchoices{position:relative;display:flex;flex-direction:column;gap:9px}.qchoice{display:flex;align-items:center;gap:13px;padding:13px 15px;border:1.5px solid var(--line);background:#fff;text-align:left;width:100%;font-size:14.5px;color:var(--ink);transition:transform .18s var(--spring),border-color .16s,box-shadow .16s,background .16s;animation:choiceIn .42s var(--spring) both}.qc-text{flex:1}.qc-mark{width:22px;height:22px;border-radius:50%;display:grid;place-items:center;color:#fff;flex-shrink:0;animation:pop .3s var(--spring)}.qc-mark.ok{background:var(--ok)}.qc-mark.no{background:var(--bad)}.qchoice:nth-child(odd){border-radius:16px 6px}.qchoice:nth-child(2n){border-radius:6px 16px}.qchoice:nth-child(1){animation-delay:.06s}.qchoice:nth-child(2){animation-delay:.13s}.qchoice:nth-child(3){animation-delay:.2s}.qchoice:nth-child(4){animation-delay:.27s}.qchoice:not(:disabled):hover{border-color:var(--emerald);transform:translateY(-2px);box-shadow:0 12px 24px -14px #12965a99}.qchoice:disabled{cursor:default}.qchoice .k{width:27px;height:27px;border-radius:var(--rk);background:var(--n-4);color:var(--emerald-2);font-weight:800;font-size:12px;display:grid;place-items:center;flex-shrink:0;border:1px solid var(--line);transition:.16s}.qchoice:not(:disabled):hover .k{background:var(--forest);color:var(--mint)}.qchoice.correct{border-color:var(--ok);background:var(--ok-soft);font-weight:600}.qchoice.correct .k{background:var(--ok);color:#fff;animation:pop .34s var(--spring)}.qchoice.wrong{border-color:var(--bad);background:var(--bad-soft);font-weight:600}.qchoice.wrong .k{background:var(--bad);color:#fff}.qchoice.dim{opacity:.4}.confused{margin:14px 0 0;font-size:12.5px;line-height:1.55;color:var(--muted)}.confused b{color:var(--ink)}.qsrc{position:relative;display:flex;align-items:center;gap:7px;width:100%;font-size:10.5px;color:var(--faint);margin:16px 0 0;padding:12px 0 0;border-top:1px dashed var(--line);font-family:var(--mono);background:none;text-align:left;transition:color .15s}button.qsrc:hover{color:var(--muted)}.qsrc .chev{margin-left:auto;transition:transform .22s var(--ease)}.qsrc.open .chev{transform:rotate(180deg)}.qcode{margin-top:11px;border-radius:12px 4px;overflow:hidden;border:1px solid #17301f;box-shadow:0 12px 26px -18px #0e2417a6;animation:codeIn .3s var(--ease) both}.qcode-h{background:#0b1c12;color:#9cc7b0;padding:8px 12px;font-family:var(--mono);font-size:10px;display:flex;align-items:center;gap:7px;border-bottom:1px solid #17301f;letter-spacing:.2px}.qcode-h .g{color:var(--mint)}.qcode pre{margin:0;padding:12px 14px;background:#0e2417;color:#cfe9db;font-family:var(--mono);font-size:11.5px;line-height:1.6;white-space:pre-wrap;word-break:break-word}@keyframes codeIn{0%{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:none}}.qsrc .sha{color:var(--emerald-2);background:var(--emerald-soft);border-radius:5px;padding:1px 6px}.grade{margin-top:15px}.grade-q{font-size:11.5px;color:var(--muted);font-weight:600;letter-spacing:.2px;margin-bottom:9px}.grade-btns{display:grid;grid-template-columns:1fr 1fr;gap:7px}.g-primary{width:100%;margin-top:8px;display:flex;align-items:center;justify-content:center;gap:8px;padding:12px;border:none;border-radius:14px 6px;background:var(--grad-dark);color:#fff;font-weight:600;font-size:13.5px;transition:transform .15s var(--spring),box-shadow .18s}.g-primary:hover{transform:translateY(-1px);box-shadow:0 12px 26px -14px #122a5280}.g{display:flex;flex-direction:column;align-items:center;gap:3px;padding:10px 4px;border:1.5px solid var(--line);border-radius:12px 4px;font-size:12.5px;font-weight:700;background:#fff;color:var(--ink);transition:transform .15s var(--spring),border-color .15s,box-shadow .15s,background .15s}.g em{font-style:normal;font-size:9.5px;color:var(--faint);font-weight:600}.g:hover{transform:translateY(-2px);box-shadow:var(--sh-1)}.g-hard:hover{border-color:#c78a1f;background:#fdf3e1;color:#a3701a}.g-easy:hover{border-color:var(--azure);background:#eef2fd;color:var(--azure-2)}.graded{margin-top:13px;font-size:12px;color:var(--muted)}.graded.unrecorded{color:var(--bad)}.qtext,.lone,.qsrc{overflow-wrap:anywhere}.results{position:relative;max-width:420px;border-radius:28px 8px;overflow:hidden;border:1px solid var(--line);box-shadow:var(--sh-2);background:linear-gradient(180deg,#fff,#fbfdfb);padding:22px;text-align:left;animation:cardIn .5s var(--spring) both}.results-top{display:flex;align-items:center;gap:16px}.results .ring .val{font-family:var(--serif);font-size:26px;font-weight:700;color:var(--forest)}.results-head h3{font-family:var(--serif);font-size:19px;color:var(--forest);margin:0 0 4px}.results-head .sub{color:var(--muted);font-size:12.5px;line-height:1.5}.missed{display:flex;flex-direction:column;gap:8px}.miss{display:flex;align-items:center;gap:10px;font-size:12.5px;color:var(--ink-2)}.miss .mx{width:20px;height:20px;flex-shrink:0;border-radius:50%;background:var(--bad-soft);color:var(--bad);display:grid;place-items:center}.miss .mq{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.results .again{width:100%;margin-top:18px}.confetti{position:absolute;top:0;left:0;right:0;height:0;pointer-events:none}.confetti i{position:absolute;top:0;width:8px;height:12px;border-radius:2px;opacity:0;animation:fall 1.4s var(--ease) forwards}.details{width:300px;border-left:1px solid var(--sep);background:var(--n-2);display:flex;flex-direction:column;flex-shrink:0;overflow-y:auto}.details .hero{padding:24px 20px 20px;text-align:center;border-bottom:1px solid var(--line)}.details .hero .mk{width:66px;height:66px;border-radius:20px 7px;overflow:hidden;margin:0 auto 13px;background:var(--grad-dark);display:grid;place-items:center;box-shadow:var(--sh-2)}.details .hero h3{font-family:var(--serif);font-size:20px;color:var(--forest);margin:0 0 2px}.details .hero .by{font-size:12px;color:var(--muted)}.details .hero .pill{display:inline-flex;align-items:center;gap:6px;margin-top:12px;font-size:11.5px;color:var(--emerald-2);background:var(--emerald-soft);border:1px solid var(--emerald-line);border-radius:99px;padding:4px 11px;font-weight:600}.details .hero .pill .p{width:7px;height:7px;border-radius:50%;background:var(--avail)}.dsec{padding:16px 20px;border-bottom:1px solid var(--line)}.dsec h4{font-size:10.5px;letter-spacing:1.2px;text-transform:uppercase;color:var(--faint);margin:0 0 10px;font-weight:700}.dsec p{margin:0;font-size:12.5px;line-height:1.55;color:var(--ink-2)}.steps{display:flex;flex-direction:column;gap:11px}.step{display:flex;gap:11px;align-items:flex-start;font-size:12.5px;color:var(--ink-2)}.step .n{width:22px;height:22px;flex-shrink:0;border-radius:7px 3px;background:var(--grad-dark);color:var(--mint);font-weight:700;font-size:11px;display:grid;place-items:center}.deck-title{margin:0 0 10px;font-size:13px;font-weight:600;color:var(--ink);line-height:1.4;word-break:break-word}.dstats{display:flex;gap:10px}.dstat{flex:1;border:1px solid var(--line);border-radius:12px 5px;padding:11px 8px;text-align:center;background:#fff}.dstat b{display:block;font-family:var(--serif);font-size:21px;color:var(--forest);line-height:1}.dstat span{font-size:10px;color:var(--faint);letter-spacing:.3px;text-transform:uppercase}.prog{display:flex;align-items:center;gap:14px;margin-bottom:14px}.ring{position:relative;flex-shrink:0;display:grid;place-items:center}.ring .rval{font-family:var(--serif);font-size:17px;color:var(--forest);letter-spacing:-.3px}.pline{margin:0;font-size:12.5px;line-height:1.5;color:var(--muted)}.pline b{color:var(--ink);font-size:14px}.runs{display:flex;flex-direction:column;gap:8px}.run{display:flex;align-items:center;gap:10px;font-size:12.5px;color:var(--ink-2)}.run .lab{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.run.now .lab{font-weight:700;color:var(--ink)}.run.next{opacity:.45}.run .sc{font-family:var(--mono);font-size:10.5px;color:var(--faint)}.run .bars{display:flex;gap:2px}.run .bars i{width:12px;height:5px;border-radius:2px;background:#e0e5e0;transition:background .3s}.run .bars i.ok{background:var(--emerald)}.run .bars i.no{background:var(--bad)}.insights{display:flex;flex-direction:column;gap:12px}.insight{display:flex;flex-direction:column;gap:5px}.insight-head{display:flex;align-items:baseline;justify-content:space-between;gap:10px;font-size:12.5px;color:var(--ink-2)}.insight-head span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.insight-head b{font-family:var(--mono);font-size:11px;color:var(--forest)}.insight-track{display:block;height:6px;border-radius:3px;background:#e1e6e2;overflow:hidden}.insight-track i{display:block;height:100%;border-radius:inherit;background:var(--emerald)}.insight small{font-size:10.5px;color:var(--faint)}@keyframes rise{0%{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}@keyframes riseR{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}@keyframes cardIn{0%{opacity:0;transform:translateY(16px) scale(.985)}to{opacity:1;transform:none}}@keyframes choiceIn{0%{opacity:0;transform:translate(-10px)}to{opacity:1;transform:none}}@keyframes pop{0%{transform:scale(.5)}60%{transform:scale(1.18)}to{transform:scale(1)}}@keyframes blink{0%,60%,to{opacity:.3;transform:translateY(0)}30%{opacity:1;transform:translateY(-3px)}}@keyframes fall{0%{opacity:1;transform:translateY(0) rotate(0)}to{opacity:0;transform:translateY(320px) rotate(320deg)}}@media(prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}.live-insights{display:none}@media(max-width:1120px){.details{display:none}.live-insights{display:block;background:var(--n-2);border:1px solid var(--sep);border-radius:8px;margin-bottom:16px}.live-insights summary{cursor:pointer;padding:12px 20px;color:var(--forest)}}@media(max-width:840px){.rail{display:none}.list{width:86px}.list .name,.list .prev,.list-top h1,.search,.tabs,.chat .time{display:none}.chat{justify-content:center;padding:9px 0;margin:0}.window{height:calc(100vh - 24px)}.stage{padding:12px}.messages{padding:18px 14px 8px}}.modes{display:flex;gap:8px;margin-top:12px;max-width:400px}.modes .start{margin-top:0;flex:1;padding:9px 6px;font-size:12.5px;background:transparent;color:var(--faint);border-color:var(--line)}.modes .start[aria-pressed=true]{background:var(--grad-dark);color:#fff;border-color:transparent}.modes .start{line-height:1.25;text-align:center}@media(max-width:620px){.modes{flex-direction:column}.modes .start{flex:1 1 auto}}
@@ -4,8 +4,8 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>FlashLearn</title>
7
- <script type="module" crossorigin src="/assets/index-CnfGuuA3.js"></script>
8
- <link rel="stylesheet" crossorigin href="/assets/index-CPLGfjHO.css">
7
+ <script type="module" crossorigin src="/assets/index-BfT0dxQA.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-BpCwS7Lt.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
package/dist/index.js CHANGED
@@ -121,7 +121,7 @@ async function runCli(args2, service2, io) {
121
121
  io.stdout(" # Generate study cards from this repository");
122
122
  io.stdout(` flashlearn generate --project ${quoteArgument(directory)}`);
123
123
  } else {
124
- if (!await generateForStudy(service2, io, directory, options)) return 1;
124
+ if (!await generateForStudy(service2, io, directory, await generationOptions(io, options))) return 1;
125
125
  io.stdout("");
126
126
  io.stdout("Next:");
127
127
  io.stdout(" # Start the local learning experience");
@@ -145,7 +145,7 @@ Then:
145
145
  Or approve empty-deck generation with start --yes.`);
146
146
  return 1;
147
147
  }
148
- if (!await generateForStudy(service2, io, directory)) return 1;
148
+ if (!await generateForStudy(service2, io, directory, await generationOptions(io))) return 1;
149
149
  }
150
150
  await service2.start(directory, options);
151
151
  const url = `http://${options.host ?? "localhost"}:${options.port ?? 4173}`;
@@ -167,6 +167,57 @@ Or approve empty-deck generation with start --yes.`);
167
167
  return 1;
168
168
  }
169
169
  }
170
+ async function generationOptions(io, options = {}) {
171
+ if (io.endpointConfigured) {
172
+ io.stderr("Using the configured inference endpoint.");
173
+ return options;
174
+ }
175
+ if (io.confirm && await io.detectCopilot?.()) {
176
+ const approved = await io.confirm("GitHub Copilot CLI was found on PATH. Use `copilot -p` to generate cards from source files? [y/N] ");
177
+ if (approved) {
178
+ io.stderr("Using GitHub Copilot CLI for code card generation.");
179
+ return { ...options, provider: { kind: "copilot" } };
180
+ }
181
+ }
182
+ if (!io.prompt) return deterministicOptions(io, options, "No interactive provider setup is available.");
183
+ const choice = (await io.prompt("Generation provider [deterministic/openai/claude/custom] (default deterministic): "))?.trim().toLowerCase();
184
+ if (!choice || choice === "deterministic") return deterministicOptions(io, options, "Deterministic generation selected.");
185
+ let provider = null;
186
+ if (choice === "openai") {
187
+ const apiKey = await requiredSecret(io, "OpenAI API key (current run only): ");
188
+ const model = await withDefault(io, "OpenAI model [gpt-4o-mini]: ", "gpt-4o-mini");
189
+ if (apiKey && model) provider = { kind: "endpoint", url: "https://api.openai.com/v1/chat/completions", model, apiKey };
190
+ } else if (choice === "claude") {
191
+ const apiKey = await requiredSecret(io, "Anthropic API key (current run only): ");
192
+ const model = await withDefault(io, "Claude model [claude-sonnet-4-5]: ", "claude-sonnet-4-5");
193
+ if (apiKey && model) provider = { kind: "anthropic", apiKey, model };
194
+ } else if (choice === "custom") {
195
+ const url = (await io.prompt("OpenAI-compatible chat-completions URL: "))?.trim();
196
+ const model = (await io.prompt("Model or deployment name: "))?.trim();
197
+ const apiKey = (await io.prompt("API key (optional, current run only): ", true))?.trim();
198
+ const authHeader = apiKey ? await withDefault(io, "Authentication header [Authorization]: ", "Authorization") : null;
199
+ if (url && model) {
200
+ if (!apiKey) provider = { kind: "endpoint", url, model };
201
+ else if (authHeader) provider = { kind: "endpoint", url, model, apiKey, authHeader };
202
+ }
203
+ } else {
204
+ return deterministicOptions(io, options, `Unknown provider "${choice}".`);
205
+ }
206
+ if (!provider) return deterministicOptions(io, options, "Provider setup was incomplete.");
207
+ io.stderr("Using the selected AI provider. Source files will be sent to that provider; the API key will not be saved.");
208
+ return { ...options, provider };
209
+ }
210
+ async function requiredSecret(io, message) {
211
+ return (await io.prompt?.(message, true))?.trim() || null;
212
+ }
213
+ async function withDefault(io, message, fallback) {
214
+ const value = await io.prompt?.(message);
215
+ return value === null || value === void 0 ? null : value.trim() || fallback;
216
+ }
217
+ function deterministicOptions(io, options, reason) {
218
+ io.stderr(`${reason} Falling back to deterministic generation; no source code will be sent to an AI provider.`);
219
+ return { ...options, provider: { kind: "deterministic" } };
220
+ }
170
221
  async function generateForStudy(service2, io, directory, options) {
171
222
  io.stderr("Generating study cards (may use the configured AI endpoint)...");
172
223
  const cards = await service2.generate(directory, options);
@@ -327,7 +378,7 @@ var init_cli = __esm({
327
378
  "use strict";
328
379
  init_paths();
329
380
  init_yaml();
330
- CLI_VERSION = "0.0.0";
381
+ CLI_VERSION = "0.2.0";
331
382
  HELP = `Usage: flashlearn <command> [directory] [options]
332
383
 
333
384
  Commands:
@@ -830,7 +881,7 @@ var init_endpoint = __esm({
830
881
  }));
831
882
  }
832
883
  /** Returns the assistant message, or null when the call fails for any reason. */
833
- async requestReply(prompt) {
884
+ async requestReply(prompt2) {
834
885
  const controller = new AbortController();
835
886
  const timeout = setTimeout(() => controller.abort(), this.config.timeoutMs ?? DEFAULT_TIMEOUT_MS);
836
887
  try {
@@ -841,7 +892,7 @@ var init_endpoint = __esm({
841
892
  model: this.config.model,
842
893
  messages: [
843
894
  { role: "system", content: SYSTEM_PROMPT },
844
- { role: "user", content: prompt }
895
+ { role: "user", content: prompt2 }
845
896
  ],
846
897
  temperature: 0
847
898
  }),
@@ -1292,6 +1343,11 @@ function createFlashLearnServer(services) {
1292
1343
  const method = request.method === "HEAD" ? "GET" : request.method;
1293
1344
  if (method === "GET" && url.pathname === "/api/cards")
1294
1345
  return json(response, 200, await services.listCards(), body);
1346
+ if (method === "GET" && url.pathname === "/api/project") {
1347
+ const declared = (await services.project?.())?.name;
1348
+ const name = typeof declared === "string" && declared.trim() ? declared.trim() : null;
1349
+ return json(response, 200, { name }, body);
1350
+ }
1295
1351
  if (method === "GET" && url.pathname === "/api/cards/next") {
1296
1352
  const card = await services.nextCard();
1297
1353
  if (!card)
@@ -1637,15 +1693,166 @@ var init_dist4 = __esm({
1637
1693
  }
1638
1694
  });
1639
1695
 
1696
+ // packages/cli/src/project-name.ts
1697
+ import { readFile as readFile4 } from "node:fs/promises";
1698
+ import { join as join6 } from "node:path";
1699
+ async function readProjectName(root) {
1700
+ for (const source of SOURCES) {
1701
+ const contents = await readFile4(join6(root, source.file), "utf8").catch(() => null);
1702
+ if (contents === null) continue;
1703
+ const name = source.read(contents)?.trim();
1704
+ if (name) return name;
1705
+ }
1706
+ return null;
1707
+ }
1708
+ var SOURCES;
1709
+ var init_project_name = __esm({
1710
+ "packages/cli/src/project-name.ts"() {
1711
+ "use strict";
1712
+ SOURCES = [
1713
+ {
1714
+ file: "package.json",
1715
+ read: (contents) => {
1716
+ try {
1717
+ const name = JSON.parse(contents).name;
1718
+ return typeof name === "string" ? name : void 0;
1719
+ } catch {
1720
+ return void 0;
1721
+ }
1722
+ }
1723
+ },
1724
+ {
1725
+ // `module k8s.io/kubernetes` names the project in its last segment.
1726
+ file: "go.mod",
1727
+ read: (contents) => contents.match(/^module\s+(\S+)/m)?.[1]?.split("/").pop()
1728
+ }
1729
+ ];
1730
+ }
1731
+ });
1732
+
1733
+ // packages/cli/src/providers.ts
1734
+ import { execFile as execFile2 } from "node:child_process";
1735
+ import { promisify as promisify2 } from "node:util";
1736
+ function promptFor(input, includeSystem = true) {
1737
+ const content = input.content.length > MAX_CONTENT_CHARS2 ? `${input.content.slice(0, MAX_CONTENT_CHARS2)}
1738
+ ... file truncated ...` : input.content;
1739
+ const prompt2 = `File: ${input.path}
1740
+ Write at most ${MAX_CARDS} cards.
1741
+
1742
+ ${content}`;
1743
+ return includeSystem ? `${SYSTEM_PROMPT2}
1744
+
1745
+ ${prompt2}` : prompt2;
1746
+ }
1747
+ function attributedCards(reply, input) {
1748
+ const fenced = /```(?:json)?\s*([\s\S]*?)```/.exec(reply);
1749
+ const candidate = fenced?.[1]?.trim() ?? reply.trim();
1750
+ const start = candidate.indexOf("{");
1751
+ const end = candidate.lastIndexOf("}");
1752
+ if (start === -1 || end <= start) return [];
1753
+ try {
1754
+ const parsed = JSON.parse(candidate.slice(start, end + 1));
1755
+ if (!Array.isArray(parsed.cards)) return [];
1756
+ return parsed.cards.flatMap((entry) => {
1757
+ if (typeof entry !== "object" || entry === null) return [];
1758
+ const { question, answer } = entry;
1759
+ if (typeof question !== "string" || typeof answer !== "string") return [];
1760
+ if (!question.trim() || !answer.trim()) return [];
1761
+ return [{ question: question.trim(), answer: answer.trim(), source: { path: input.path, sha: input.sha } }];
1762
+ }).slice(0, MAX_CARDS);
1763
+ } catch {
1764
+ return [];
1765
+ }
1766
+ }
1767
+ async function runCopilot(prompt2) {
1768
+ try {
1769
+ const { stdout } = await execFileAsync2("copilot", [
1770
+ "-p",
1771
+ prompt2,
1772
+ "--silent",
1773
+ "--no-custom-instructions",
1774
+ "--no-ask-user",
1775
+ "--no-color"
1776
+ ], { timeout: 12e4, maxBuffer: 1e6 });
1777
+ return stdout;
1778
+ } catch {
1779
+ return null;
1780
+ }
1781
+ }
1782
+ var execFileAsync2, CODE_EXTENSIONS2, MAX_CONTENT_CHARS2, MAX_CARDS, SYSTEM_PROMPT2, CopilotExtractor, AnthropicExtractor;
1783
+ var init_providers = __esm({
1784
+ "packages/cli/src/providers.ts"() {
1785
+ "use strict";
1786
+ execFileAsync2 = promisify2(execFile2);
1787
+ CODE_EXTENSIONS2 = /\.(go|js|jsx|ts|tsx)$/i;
1788
+ MAX_CONTENT_CHARS2 = 24e3;
1789
+ MAX_CARDS = 5;
1790
+ SYSTEM_PROMPT2 = 'Write onboarding flashcards grounded only in the supplied source file. Prefer behavior, control flow, and intent. Never invent APIs, paths, or behavior. Reply with JSON only: {"cards":[{"question":"...","answer":"..."}]}. Return an empty cards array when nothing is worth asking.';
1791
+ CopilotExtractor = class {
1792
+ constructor(run = runCopilot) {
1793
+ this.run = run;
1794
+ }
1795
+ async extract(input) {
1796
+ if (!CODE_EXTENSIONS2.test(input.path) || input.content.trim().length === 0) return [];
1797
+ const reply = await this.run(promptFor(input));
1798
+ return reply === null ? [] : attributedCards(reply, input);
1799
+ }
1800
+ };
1801
+ AnthropicExtractor = class {
1802
+ constructor(apiKey, model, fetchImpl = fetch) {
1803
+ this.apiKey = apiKey;
1804
+ this.model = model;
1805
+ this.fetchImpl = fetchImpl;
1806
+ }
1807
+ async extract(input) {
1808
+ if (!CODE_EXTENSIONS2.test(input.path) || input.content.trim().length === 0) return [];
1809
+ const controller = new AbortController();
1810
+ const timeout = setTimeout(() => controller.abort(), 3e4);
1811
+ try {
1812
+ const response = await this.fetchImpl("https://api.anthropic.com/v1/messages", {
1813
+ method: "POST",
1814
+ headers: {
1815
+ "content-type": "application/json",
1816
+ "x-api-key": this.apiKey,
1817
+ "anthropic-version": "2023-06-01"
1818
+ },
1819
+ body: JSON.stringify({
1820
+ model: this.model,
1821
+ max_tokens: 2e3,
1822
+ system: SYSTEM_PROMPT2,
1823
+ messages: [{ role: "user", content: promptFor(input, false) }]
1824
+ }),
1825
+ signal: controller.signal
1826
+ });
1827
+ if (!response.ok) return [];
1828
+ const payload = await response.json();
1829
+ const reply = payload.content?.find((part) => part.type === "text")?.text;
1830
+ return typeof reply === "string" ? attributedCards(reply, input) : [];
1831
+ } catch {
1832
+ return [];
1833
+ } finally {
1834
+ clearTimeout(timeout);
1835
+ }
1836
+ }
1837
+ };
1838
+ }
1839
+ });
1840
+
1640
1841
  // packages/cli/src/production.ts
1641
1842
  import { stat } from "node:fs/promises";
1642
- import { join as join6 } from "node:path";
1843
+ import { join as join7 } from "node:path";
1643
1844
  function createProductionDependencies() {
1644
1845
  return {
1645
1846
  initializeStore,
1646
- generateCards: (root, options) => generateCards(root, void 0, options),
1647
- createCardRepository: (root) => new JsonCardRepository(join6(flashlearnRoot(root), "cards.json")),
1648
- createReviewRepository: (root) => new JsonReviewRepository(join6(flashlearnRoot(root), "review.json")),
1847
+ generateCards: (root, options) => {
1848
+ if (!options?.provider) return generateCards(root, void 0, options);
1849
+ const { provider, ...scope } = options;
1850
+ const extractor = extractorFor(provider);
1851
+ const concurrency = provider.kind === "copilot" ? 1 : 8;
1852
+ return new ExtractionService(extractor, concurrency).generateFromRepository(root, scope);
1853
+ },
1854
+ createCardRepository: (root) => new JsonCardRepository(join7(flashlearnRoot(root), "cards.json")),
1855
+ createReviewRepository: (root) => new JsonReviewRepository(join7(flashlearnRoot(root), "review.json")),
1649
1856
  scheduleReview,
1650
1857
  selectNextCard,
1651
1858
  createServer: createFlashLearnServer,
@@ -1660,6 +1867,7 @@ function createProductionDependencies() {
1660
1867
  });
1661
1868
  });
1662
1869
  },
1870
+ readProjectName,
1663
1871
  isDirectory: async (path) => {
1664
1872
  try {
1665
1873
  return (await stat(path)).isDirectory();
@@ -1671,6 +1879,11 @@ function createProductionDependencies() {
1671
1879
  now: () => /* @__PURE__ */ new Date()
1672
1880
  };
1673
1881
  }
1882
+ function extractorFor(provider) {
1883
+ if (provider.kind === "deterministic") return deterministicExtractor();
1884
+ const code = provider.kind === "copilot" ? new CopilotExtractor() : provider.kind === "anthropic" ? new AnthropicExtractor(provider.apiKey, provider.model) : new EndpointExtractor(provider);
1885
+ return new CompositeExtractor(code, new MarkdownExtractor());
1886
+ }
1674
1887
  var init_production = __esm({
1675
1888
  "packages/cli/src/production.ts"() {
1676
1889
  "use strict";
@@ -1679,6 +1892,8 @@ var init_production = __esm({
1679
1892
  init_dist3();
1680
1893
  init_dist4();
1681
1894
  init_paths();
1895
+ init_project_name();
1896
+ init_providers();
1682
1897
  }
1683
1898
  });
1684
1899
 
@@ -1727,6 +1942,7 @@ var init_workstream5 = __esm({
1727
1942
  const reviews = this.dependencies.createReviewRepository(rootPath);
1728
1943
  const services = {
1729
1944
  listCards: () => cards.list(),
1945
+ project: async () => ({ name: await this.dependencies.readProjectName(root) }),
1730
1946
  getCard: (id) => cards.get(id),
1731
1947
  nextCard: async () => {
1732
1948
  const allCards = await cards.list();
@@ -1793,6 +2009,7 @@ var init_workstream5 = __esm({
1793
2009
 
1794
2010
  // packages/cli/src/confirm.ts
1795
2011
  import { createInterface } from "node:readline/promises";
2012
+ import { Writable } from "node:stream";
1796
2013
  async function confirm(message) {
1797
2014
  if (!process.stdin.isTTY || !process.stderr.isTTY) return false;
1798
2015
  const reader = createInterface({ input: process.stdin, output: process.stderr });
@@ -1803,12 +2020,48 @@ async function confirm(message) {
1803
2020
  reader.close();
1804
2021
  }
1805
2022
  }
2023
+ async function prompt(message, secret = false) {
2024
+ if (!process.stdin.isTTY || !process.stderr.isTTY) return null;
2025
+ if (!secret) {
2026
+ const reader2 = createInterface({ input: process.stdin, output: process.stderr });
2027
+ try {
2028
+ return await reader2.question(message);
2029
+ } finally {
2030
+ reader2.close();
2031
+ }
2032
+ }
2033
+ process.stderr.write(message);
2034
+ const muted = new Writable({ write(_chunk, _encoding, callback) {
2035
+ callback();
2036
+ } });
2037
+ const reader = createInterface({ input: process.stdin, output: muted, terminal: true });
2038
+ try {
2039
+ const answer = await reader.question("");
2040
+ process.stderr.write("\n");
2041
+ return answer;
2042
+ } finally {
2043
+ reader.close();
2044
+ }
2045
+ }
1806
2046
  var init_confirm = __esm({
1807
2047
  "packages/cli/src/confirm.ts"() {
1808
2048
  "use strict";
1809
2049
  }
1810
2050
  });
1811
2051
 
2052
+ // packages/cli/src/copilot.ts
2053
+ import { execFile as execFile3 } from "node:child_process";
2054
+ function detectCopilot() {
2055
+ return new Promise((resolve4) => {
2056
+ execFile3("copilot", ["--version"], { timeout: 5e3 }, (error) => resolve4(error === null));
2057
+ });
2058
+ }
2059
+ var init_copilot = __esm({
2060
+ "packages/cli/src/copilot.ts"() {
2061
+ "use strict";
2062
+ }
2063
+ });
2064
+
1812
2065
  // packages/cli/src/index.ts
1813
2066
  var src_exports = {};
1814
2067
  var dependencies, service;
@@ -1819,13 +2072,17 @@ var init_src = __esm({
1819
2072
  init_production();
1820
2073
  init_workstream5();
1821
2074
  init_confirm();
2075
+ init_copilot();
1822
2076
  dependencies = createProductionDependencies();
1823
2077
  service = new CliService(dependencies);
1824
2078
  process.exitCode = await runCli(process.argv.slice(2), service, {
1825
2079
  cwd: process.cwd(),
1826
2080
  stdout: (message) => console.log(message),
1827
2081
  stderr: (message) => console.error(message),
1828
- confirm
2082
+ confirm,
2083
+ prompt,
2084
+ detectCopilot,
2085
+ endpointConfigured: Boolean(process.env.FLASHLEARN_ENDPOINT_URL?.trim() && process.env.FLASHLEARN_ENDPOINT_MODEL?.trim())
1829
2086
  });
1830
2087
  }
1831
2088
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flashlearnai/cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Source-attributed study cards for faster codebase onboarding.",
5
5
  "type": "module",
6
6
  "license": "MIT",