TutorLoom
기업·대학·교육기관을 위한 학습 플랫폼, 무료 오픈소스이며 엔터프라이즈 버전 제공
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Static site (plain HTML/CSS/JS, no build step needed to serve). Works as-is on GitHub Pages.
Brand / URL / contact: assets/js/config.js (BRAND, TAGLINE, SITE_URL, EMAIL, GOOGLE_FORM_URL). tools/build_pages.py reads it and writes the values into every page, so after a change re-run the build. tools/make_images.py redraws the og:image and favicons.
Questions: data/questions.json is generated by /workspace/.venv-edu/bin/python tools/build_questions.py
(needs sympy: python3 -m venv .venv && .venv/bin/pip install sympy; the script stops with a clear message without it). The script checks every
answer: 4 distinct options with exactly one correct, numeric answers recomputed, and puzzles, seating, routes and
tournaments brute-forced. Edit the script and re-run it; don't hand-edit the JSON.
Fields: topic, difficulty, answer (0-based), optional data (HTML table/chart), set (shared DILR data),
fixed (keep the option order; otherwise options are shuffled at runtime).
CAT track: data/cat.json is also written by build_questions.py, from tools/cat/qa.py, dilr.py and
varc.py (helpers in tools/cat/common.py). Format: {sections:{varc,dilr,qa:{name,questions}}, sets:{id:{kind,title,html}}}.
Questions have type mcq (0-based answer) or tita (answer string; kind number or sequence).
Build checks:
In solutions, refer to options as "Option 1"…"Option 4" (bank order); assets/js/exam.js rewrites them to the
shuffled letters at runtime.
Exam engine: assets/js/exam.js is shared by CAT and CUET. A page sets window.EXAM = "cat" or "cuet" before loading it;
the per-exam JSON, marking, timing and sectional picks are in the EXAMS config. Pages: cat-practice.html, cuet-practice.html. URL params mode=sectional|practice, section,
topic).
CUET (UG) track: data/cuet.json (same format; sections gat, english, maths; every item is an MCQ with a part).
Content is in tools/cuet/gat.py, english.py and maths.py (helpers in tools/cuet/ucommon.py).
Build checks:
cuet_part tag and are reused in the GAT with source: "general", orig_id and the id G-<id>.
Permutations & Combinations and Probability are not reused (they aren't in the GAT syllabus).
The site counts these shared items only once in its totals.Sectional mode: +5 / −1 / 0, 72 s per question (50 questions = 60 min). GAT picks QR 15, LR 15, GMA 10, GK 10. English and Maths have fewer than 50 questions, so their timer is pro-rated. Section B2 (Applied Maths) isn't written yet.
VARC rules sheet: FORMULAS["varc"] in tools/formulas.py.
Pages: python3 tools/build_pages.py regenerates the HTML pages. It refuses to build if any listed topic
has fewer than 4 questions. Counts on the site (hero stats, topic badges) are read live from questions.json, cat.json and cuet.json (assets/js/stats.js).
Run locally: python3 -m http.server 8765 → http://localhost:8765 (the quiz fetches JSON, so file:// won't work).
mock-tests.html and placements.html are placeholders (noindex, not linked from the nav or footer).
Info pages (about, faq, privacy, terms, contact, 404), sitemap.xml and robots.txt are generated by tools/pages_info.py.
Analytics (optional): set GOATCOUNTER in assets/js/config.js to your GoatCounter code and rebuild; empty means no script.
Events (via window.track, a no-op without GoatCounter), pattern <mode>-<event>-<exam>-<section>:
quiz-{start,finish,autosubmit}-general-{quant,reasoning,dilr,mixed},
{sectional,practice}-{start,finish}-cat-{varc,dilr,qa}, sectional-autosubmit-cat-{varc,dilr,qa},
{sectional,practice}-{start,finish}-cuet-{gat,english,maths}, sectional-autosubmit-cuet-{gat,english,maths}.
utm_* parameters in the URL are carried over to same-site links (assets/js/layout.js).
cat-sets)tools/assign_levels.py (run by build_questions.py, deterministic) gives every CAT question a level and, if it is in a ready set, a practiceSet (numbered Easy, then Medium, then Hard; slots per level set per section in SLOTS, e.g. Quant Easy 1–5, Medium 6–13, Hard 14–18, VARC/DILR 1–5/6–10/11–15; 10 questions each; passages/data sets never split, majority level with ties to the harder; a DILR set = 2 data sets of 5). It writes data/cat/sets.json. Pages: cat-quant-sets.html, cat-varc-sets.html, cat-dilr-sets.html, set.html?sec=qa|varc|dilr&set=N (reuses exam.js).assets/js/sets.js): Easy is open (guests: Easy set 1 only). A level unlocks once every ready set of the previous level is completed; a previous level with 0 ready sets counts as complete. Completion comes only from Supabase set_progress rows. This is a UI gate only: the question JSON is public.assets/js/auth.js (Supabase email magic link, supabase-js 2.118.0 vendored in assets/js/vendor/). Fill SUPABASE_URL and SUPABASE_ANON_KEY in config.js, run supabase/schema.sql, then rebuild (the CSP picks up the Supabase domain). Test-only mock: ?mock=1 (not linked; ?mock=0 to leave).build_pages.py writes a CSP meta tag on every page and moves any inline <script> into assets/js/page/ (the build fails on inline scripts or on*= handlers).quant-lab)Visual CAT Quant practice: quant-lab.html (hub), quant-lab-explainers.html, quant-lab-generator.html, quant-lab-concept-lab.html, quant-lab-practice.html. The pages are built by tools/build_pages.py. The scripts live in assets/js/quant-lab/ and the styles in assets/css/quant-lab.css, using site tokens only, so light and dark follow prefers-color-scheme. There are 23 topics and 207 question types. The pages say "Questions generated by software and checked by our team." The lab adds no cookies, ads or analytics. Practice stats are stored only in localStorage (aptisetu.quantlab.v1).
Tests (dev-only; Playwright and axe-core are not shipped):
RUNS=1000 node tests/quant-lab/generator.test.js: every type is checked against an independent solver.RUNS=1000 node tests/quant-lab/viz.test.js: every visual is checked for valid SVG.NODE_PATH=<playwright+axe-core> BASE=http://127.0.0.1:PORT/ node tests/quant-lab/e2e.js: covers 390/1280 × light/dark, every type and explainer, the labs and practice, hostile URL parameters, console errors, CSP violations, axe colour-contrast and reduced motion.tools/qpages.py (run by build_pages.py) writes q/<track>/<id>-<topic>.html for every open-bank question (CAT, CUET, Bank/SSC), topic hubs q/<track>/<section>-<topic>.html and q/index.html; all go into sitemap.xml. Excluded on purpose: CAT mock and fixed sectional packs (kept unseen) and CUET items reused from the general bank (one page each, in Bank/SSC). Pages use <base href="/"> so shared relative links work; JSON-LD Quiz per page.cat-practice.html?daily=1, cuet-practice.html?daily=1 (exam.js) and practice.html?daily=1 (quiz.js): 10 standalone open-bank questions, same for everyone on a date (date-seeded shuffle). Linked from the home page.localStorage["aptisetu.history"] after 2+ attempts of the same test type.tests/features.js (same setup as tests/flow.js).supabase/migrations/ (accounts, history, passes, Open Mock with server-side scoring, pass_gating flag default false).create-order, verify-payment (HMAC order_id|payment_id), razorpay-webhook (raw-body signature). Live keys refused unless ALLOW_LIVE_PAYMENTS=true; price from PASS_PRICE_PAISE.SUPABASE_ACCESS_TOKEN SUPABASE_PROJECT_REF SUPABASE_DB_PASSWORD RAZORPAY_KEY_ID RAZORPAY_KEY_SECRET RAZORPAY_WEBHOOK_SECRET PASS_PRICE_PAISE, then bash tools/deploy_supabase.sh and python3 tools/set_supabase_config.py. Secrets are never printed or committed.PAYMENTS_ENABLED:"on" in config.js and pass_gating=true in app_settings.node tests/supabase/sql.test.mjs (needs @electric-sql/pglite), deno test supabase/functions/_shared/razorpay_test.ts, node tests/month3.js.tools/flashcards.py (run by build_pages.py) turns the checked printable sheets in tools/sheets.py into flashcards.html and assets/js/flashcards-data.js (76 cards, 3 decks). Fronts are generated from each formula (text up to the first =, ⇒ or ⇔); odd ones are set in FC_FRONT. Progress ("known") stays in the viewer's browser. Each formula sheet links to its deck (flashcards.html#<slug>).q/index.html, difficulty chips on topic hubs (assets/js/qfilter.js).placements.html.tests/phase1.js (same setup as tests/flow.js).assets/js/home-goal.js, choice kept in aptisetu.goal); contact report builder that opens a pre-filled email or copies the text (assets/js/contact.js, contact.html?q=<id> pre-fills the ID); backup code on login.html#backup to move local progress between devices without an account (assets/js/backup.js, only progress keys, never login tokens); WebSite JSON-LD on the home page. Tests: tests/phase1b.js.