Something went wrong. Try again.
the everything app :D cedarplan.dunkirk.sh
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508/* Catppuccin Macchiato, matching the palette in the repo README. */:root { --base: #24273a; --mantle: #1e2030; --crust: #181926; --surface: #363a4f; --overlay: #6e738d; --text: #cad3f5; --subtext: #a5adcb; --lavender: #b7bdf8; --green: #a6da95; --yellow: #eed49f; --peach: #f5a97f; --red: #ed8796; --sky: #91d7e3;}
* { box-sizing: border-box;}
body { margin: 0 auto; max-width: 1400px; padding: 28px 32px 64px; background: var(--base); color: var(--text); font: 13px / 1.6 "JetBrains Mono", "SF Mono", ui-monospace, monospace;}
header { display: flex; align-items: baseline; gap: 14px; margin-bottom: 20px;}
h1 { margin: 0; font-size: 15px; font-weight: 500; letter-spacing: 0.01em;}
#who,#status { margin: 0; color: var(--overlay); font-size: 12px;}
#status.err { color: var(--red);}
#controls { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 14px; padding-bottom: 16px; border-bottom: 1px solid var(--surface);}
.field { display: flex; flex-direction: column; gap: 4px; min-width: 0;}
.actions { display: flex; gap: 8px; margin-left: auto;}
label { color: var(--overlay); font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase;}
#status { margin: 10px 0 0; min-height: 1.4em; color: var(--subtext); font-size: 12px;}
#status.err { color: var(--red);}
#status.ok { color: var(--green);}
select,button { font: inherit; color: var(--text); background: var(--mantle); border: 1px solid var(--surface); border-radius: 3px; padding: 6px 10px;}
select { max-width: 300px;}
select:focus-visible,button:focus-visible { outline: 2px solid var(--lavender); outline-offset: 2px;}
button { cursor: pointer; transition: border-color 0.12s ease, background 0.12s ease, opacity 0.12s ease;}
button:hover:not(:disabled) { border-color: var(--lavender);}
button:disabled { opacity: 0.4; cursor: default;}
#capture { color: var(--crust); background: var(--lavender); border-color: var(--lavender);}
nav { display: flex; gap: 6px; margin: 18px 0;}
nav button.on { color: var(--crust); background: var(--lavender); border-color: var(--lavender);}
/* ---- tree ---- */
.program { margin-bottom: 26px;}
.program > h2 { margin: 0 0 4px; font-size: 13px; font-weight: 500;}
.credits { margin-bottom: 12px; color: var(--overlay); font-size: 12px;}
details { border-left: 1px solid var(--surface); margin: 2px 0 2px 4px; padding-left: 12px;}
summary { cursor: pointer; padding: 3px 0; list-style: none;}
summary::-webkit-details-marker { display: none;}
summary::before { content: "▸ "; color: var(--overlay);}
details[open] > summary::before { content: "▾ ";}
.group { padding: 4px 0 4px 14px; border-left: 1px solid var(--surface); margin-left: 4px;}
/* A status dot carries completion; the label carries planning. */.dot { display: inline-block; width: 7px; height: 7px; margin-right: 7px; border-radius: 50%; vertical-align: middle; background: var(--overlay);}.dot.Completed { background: var(--green);}.dot.PartiallyCompleted { background: var(--yellow);}.dot.NotStarted { background: var(--surface); border: 1px solid var(--overlay);}
.tag { margin-left: 8px; padding: 1px 6px; border-radius: 3px; background: var(--mantle); color: var(--subtext); font-size: 11px;}.tag.rule { color: var(--peach);}.tag.planned { color: var(--sky);}
.courses { margin: 5px 0 2px; color: var(--subtext); font-size: 12px; word-spacing: 2px;}
.muted { color: var(--overlay);}
/* ---- overlap ---- */
.pool { padding: 10px 0; border-bottom: 1px solid var(--surface);}
.pool .sides { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; margin-bottom: 6px;}
.pool .side b { display: block; color: var(--lavender); font-weight: 500;}
.count { color: var(--green);}
.unresolved li { margin-bottom: 5px; color: var(--subtext);}
table { border-collapse: collapse; margin-bottom: 18px;}
td { padding: 2px 16px 2px 0;}
/* ---- schedule ---- */
.week { margin-bottom: 24px;}
.clash { margin: 4px 0; padding: 5px 9px; border-left: 2px solid var(--red); background: var(--mantle); color: var(--red);}
.toggle { display: inline-flex; align-items: center; gap: 7px; margin-top: 12px; color: var(--subtext); font-size: 11px; text-transform: none; letter-spacing: 0; cursor: pointer;}
/* ---- the install page, and the link to it ---- */
#get-bridge { margin-left: auto; padding: 4px 10px; border: 1px solid var(--lavender); border-radius: 3px; color: var(--lavender); font-size: 11px; text-decoration: none;}
#get-bridge:hover { background: color-mix(in oklch, var(--lavender) 14%, transparent);}
#get-bridge[hidden] { display: none;}
.install { max-width: 68ch;}
.install h2 { margin: 26px 0 8px; color: var(--subtext); font-size: 12px; font-weight: 500; letter-spacing: 0.04em; text-transform: uppercase;}
.install ol,.install ul { margin: 0; padding-left: 20px;}
.install li { margin-bottom: 8px;}
.install code { padding: 1px 5px; border-radius: 3px; background: var(--mantle); color: var(--sky);}
.install a.export { display: inline-block; margin-right: 8px; text-decoration: none;}
#version.ok { color: var(--green);}
#version.err { color: var(--peach);}
/* ---- the semester bar ---- */
.term-bar { display: flex; flex-wrap: wrap; align-items: baseline; gap: 12px; margin-bottom: 4px;}
.term-bar h2 { margin: 0; color: var(--text); font-size: 15px; font-weight: 500; letter-spacing: 0.02em;}
/* ---- the term's courses, one accordion each ---- */
.here { margin-top: 18px;}
details.course { border-left: 2px solid var(--surface); margin-bottom: 2px;}
details.course.mine { border-left-color: var(--lavender);}
summary.course-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 10px; padding: 5px 10px; border-radius: 3px; cursor: pointer;}
summary.course-head:hover { background: var(--mantle);}
/* The section the week is holding, which is the only one worth spelling out. */.slot { color: var(--sky); font-size: 11px; font-variant-numeric: tabular-nums;}
.slot.mine { color: var(--lavender);}
.slot.none { color: var(--red);}
.slot-seats { font-size: 11px;}
summary.course-head .release { margin-left: auto; padding: 0 5px; border: 0; background: transparent; color: var(--overlay); font: inherit; line-height: 1; cursor: pointer;}
summary.course-head .release:hover { color: var(--red);}
summary.course-head .release + .release { margin-left: 0;}
/* ---- choosing between the sections ---- */
.options { padding: 2px 0 8px 12px;}
.option { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; padding: 3px 8px; border-radius: 3px; cursor: pointer;}
.option:hover { background: var(--mantle);}
.option input { accent-color: var(--lavender);}
.option .num { min-width: 3ch; color: var(--subtext); font-weight: 500; font-variant-numeric: tabular-nums;}
.option.in { background: color-mix(in oklch, var(--sky) 8%, transparent);}
.option.in .num,.option.in .when { color: var(--sky);}
.option.mine { background: color-mix(in oklch, var(--lavender) 14%, transparent);}
.option.mine .num,.option.mine .when { color: var(--lavender);}
/* Taking this one would cost something else its place. */.option.clashes .when { color: var(--peach);}
.option.clashes:not(.mine) .num { color: var(--peach);}
/* Whose choice it is, in two words at the end of the row. */.mark { margin-left: auto; color: var(--overlay); font-size: 10px; letter-spacing: 0.04em; text-transform: uppercase;}
.option.in .mark { color: var(--sky);}
.option.mine .mark { color: var(--lavender);}
.option.auto .when { color: var(--overlay); font-style: italic;}
.term h3 .open-term { margin-left: auto; padding: 1px 7px; border: 1px solid var(--surface); border-radius: 3px; background: transparent; color: var(--subtext); font: inherit; font-size: 10px; cursor: pointer;}
.term h3 .open-term:hover { border-color: var(--lavender); color: var(--lavender);}
/* ---- the week grid ---- */
.grid { display: flex; gap: 6px; margin-top: 12px; padding-bottom: 4px; overflow-x: auto;}
.gutter { position: relative; flex: 0 0 52px;}
.hour { position: absolute; right: 6px; color: var(--overlay); font-size: 10px; transform: translateY(20px);}
.day { position: relative; flex: 1 0 132px; border-left: 1px solid var(--surface); /* An hour rule every two rows, so a block can be read against the clock without tracking back to the gutter. Offset by the weekday heading. */ background-image: repeating-linear-gradient( to bottom, color-mix(in oklch, var(--surface) 55%, transparent) 0 1px, transparent 1px 40px ); background-position: 0 26px;}
.day h3 { margin: 0; padding: 4px 0 0 8px; color: var(--subtext); font-size: 11px; font-weight: 500;}
.block { position: absolute; left: 0; cursor: pointer; box-sizing: border-box; width: 100%; margin-right: 4px; overflow: hidden; padding: 3px 6px; border-radius: 3px; border-left: 2px solid var(--surface); background: var(--mantle); font-size: 11px; line-height: 1.3;}
/* The three things a block can be, in order of how settled it is. */.block.pinned { border-left-color: var(--lavender); background: color-mix(in oklch, var(--lavender) 16%, var(--mantle));}
.block.pinned b { color: var(--lavender);}
/* The arranger's, until the student says otherwise. */.block.suggested { border-left-color: var(--sky); background: color-mix(in oklch, var(--sky) 10%, var(--mantle));}
.block.suggested b { color: var(--sky);}
/* The section being hovered, where it would fall if it were taken. */.block.ghost { border: 1px dashed var(--overlay); border-left: 2px dashed var(--green); background: color-mix(in oklch, var(--green) 10%, var(--mantle));}
.block.ghost b { color: var(--green);}
.block.ghost.clash { border-left-color: var(--peach); background: color-mix(in oklch, var(--peach) 12%, var(--mantle));}
.block.ghost.clash b { color: var(--peach);}
.block.clash { border-left-color: var(--red); background: color-mix(in oklch, var(--red) 13%, var(--mantle));}
.block.clash b { color: var(--red);}
.block b { display: block; color: var(--lavender); font-weight: 500;}
.block span { color: var(--subtext);}
/* A topics course's subject, which is what tells its sections apart. */.block .topic { display: block; overflow: hidden; color: var(--text); font-style: normal; text-overflow: ellipsis; white-space: nowrap;}
.option .topic { color: var(--text); font-weight: 500;}
/* ---- course cards ---- */
details.req { margin-bottom: 18px; border-left: 1px solid var(--surface);}
details.req > summary { padding: 5px 0; color: var(--subtext);}
.code { min-width: 88px; color: var(--text); font-weight: 500;}
.title { flex: 1; min-width: 140px; color: var(--subtext);}
.cr,.count { color: var(--overlay); font-size: 11px;}
.gate { padding: 1px 7px; border-radius: 3px; font-size: 10px; letter-spacing: 0.04em; text-transform: uppercase;}
.gate.open { color: var(--green); background: color-mix(in oklch, var(--green) 14%, transparent);}
.gate.blocked { color: var(--overlay); background: var(--mantle);}
.gate.unknown { color: var(--yellow); background: color-mix(in oklch, var(--yellow) 12%, transparent);}
.tag.unlocks { color: var(--sky); cursor: help;}
.gate-why { margin: 0 0 4px 10px; color: var(--peach); font-size: 11px;}
.when { min-width: 210px; font-variant-numeric: tabular-nums;}
.room,.who { color: var(--overlay); font-size: 11px;}
.who { flex: 1; min-width: 120px;}
.tag.seats { font-variant-numeric: tabular-nums;}
.tag.live { border-bottom: 1px dotted currentColor;}
.tag.open { color: var(--green);}
.tag.full { color: var(--red);}
.seats-note { margin: 2px 0 8px; font-size: 11px;}
@media (max-width: 720px) { .when { min-width: 0; }
.title { min-width: 0; }
.grid { margin-left: -16px; margin-right: -16px; padding-left: 16px; }}
/* ---- the plan view ---- */
.plan-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; padding-bottom: 14px; border-bottom: 1px solid var(--surface);}
.plan-controls input[type="range"] { width: 150px; accent-color: var(--lavender);}
.plan-controls .cr { min-width: 2ch; color: var(--lavender); font-variant-numeric: tabular-nums;}
.term { margin-bottom: 14px; padding-left: 12px; border-left: 2px solid var(--surface);}
.term.summer { border-left-color: var(--yellow);}
.term.unplaced { border-left-color: var(--red);}
.term h3 { display: flex; align-items: baseline; gap: 10px; margin: 0 0 5px; font-size: 12px; font-weight: 500;}
.term h3 .cr { color: var(--overlay); font-size: 11px;}
.plan-course { display: flex; gap: 10px; padding: 2px 0; font-size: 12px;}
.plan-course b { min-width: 92px; color: var(--text); font-weight: 500;}
.plan-course span { color: var(--subtext);}
/* ---- editing the plan ---- * * A plan you can argue with has to look like one: a row you can pick up, a * term that says yes or no while you hold it, and a way out of every edit. */
.term.empty { border-left-style: dashed; opacity: 0.65;}
.term.dropped { border-left-color: var(--overlay);}
.term.drop-ok { border-left-color: var(--green);}
.term.drop-bad { border-left-color: var(--red);}
.term.over { background: var(--mantle);}
.plan-course { align-items: baseline; border-radius: 3px; cursor: grab;}
.plan-course:hover { background: var(--mantle);}
.plan-course.moved b::before { content: "\00b7 "; color: var(--lavender);}
/* Out of the way until the row is under the pointer, so a column of buttons * never competes with the courses it belongs to. */.plan-course .release { margin-left: auto; padding: 0 5px; border: none; background: none; color: var(--overlay); font-size: 12px; line-height: 1; opacity: 0;}
.plan-course:hover .release,.plan-course .release:focus-visible,/* A pin is state, not chrome: the way out of it stays on screen. */.plan-course.moved .release { opacity: 1;}
.plan-course .release:hover { color: var(--red);}
/* What the term would become, said while the course is still in the air: * a native tooltip never appears during a drag. */.term h3 .note { color: var(--lavender); font-size: 11px; font-weight: 400;}
.term.drop-bad h3 .note { color: var(--red);}
.plan-course.why { padding: 0 0 3px 12px; color: var(--red); font-size: 11px; cursor: default;}
.term h3 .add { margin-left: auto; padding: 0 6px; border: none; background: none; color: var(--overlay); font-size: 13px; line-height: 1;}
.term h3 .add:hover { color: var(--lavender);}
.graph-add { display: flex; align-items: center; gap: 6px; margin-bottom: 8px; font-size: 12px;}
.graph-add .add-course,.graph-add .add-term { padding: 2px 6px; font: inherit; color: var(--text); background: var(--mantle); border: 1px solid var(--surface); border-radius: 3px;}
.graph-add .add-course { width: 26ch;}
.term h3 .add-course { width: 12ch; padding: 1px 4px; font: inherit; font-size: 11px; color: var(--text); background: var(--mantle); border: 1px solid var(--surface); border-radius: 3px;}
.plan-controls .regenerate[hidden] { display: none;}
/* What a sync would do, shown in full before anything is written. */.sync:not(:empty) { margin: 0 0 16px; padding: 12px 14px; border: 1px solid var(--surface); border-left: 2px solid var(--lavender); border-radius: 3px; background: var(--mantle);}
.sync h3 { margin: 0 0 8px; font-size: 12px; font-weight: 500;}
.sync-change { padding: 2px 0; font-size: 12px; color: var(--text);}
.sync-change.muted { color: var(--overlay);}
/* The reason something failed, which is the one line worth reading. */.sync .why { margin: 0; color: var(--red); font-size: 12px;}
/* Thirty round trips is long enough to need watching. */.sync .bar { height: 6px; margin: 6px 0 10px; border-radius: 3px; background: var(--surface); overflow: hidden;}
.sync .bar span { display: block; height: 100%; min-width: 2px; border-radius: 3px; background: var(--lavender); transition: width 0.2s ease;}
.sync-actions { display: flex; gap: 8px; margin-top: 10px;}
/* ---- build ---- */
.picker { margin-bottom: 18px; padding-bottom: 16px; border-bottom: 1px solid var(--surface);}
.picker h2 { margin: 0 0 8px; font-size: 13px; font-weight: 500;}
.picker select { display: block; width: 100%; max-width: 520px; margin-bottom: 10px;}
/* Spacing belongs to the row, not to each chip: a margin on the child leaves the gap collapsed wherever two chips sit next to each other in the markup. */.chips { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px; margin-bottom: 10px;}
.chips .tag { margin: 0;}
.tag.on { color: var(--green);}
.tag.prog { color: var(--lavender);}
.shared { color: var(--green);}
.choice { margin-bottom: 18px; padding: 12px 14px; background: var(--mantle); border: 1px solid var(--surface); border-radius: 4px;}
.choice h3 { display: flex; align-items: baseline; gap: 8px; margin: 0 0 8px; font-size: 13px; font-weight: 500;}
.choice h3 .cr { margin-left: auto; color: var(--overlay); font-size: 11px;}
.candidate { display: flex; align-items: baseline; gap: 8px; padding: 3px 0;}
.candidate .title { flex: 1; color: var(--subtext); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;}
.candidate.picked { color: var(--green);}
/* The pick control is the only affordance in a dense list, so it stays a fixed width: a row that reflows when its label changes is unclickable. */.pick { width: 26px; padding: 0; color: var(--subtext); line-height: 20px; text-align: center;}
.candidate.picked .pick { color: var(--crust); background: var(--green); border-color: var(--green);}
/* Cost, ranked by how much it should worry you. */.tag.free { color: var(--green);}
.tag.cheap { color: var(--yellow);}
.tag.bad { color: var(--red);}
.tag.trying { color: var(--yellow);}
/* Sits inside the chip, so it reads as part of it rather than a stray control. */.tag .drop { margin-left: 6px; padding: 0; color: inherit; background: none; border: none; font-size: 12px; line-height: 1; cursor: pointer;}
.tag .drop:hover:not(:disabled) { color: var(--red);}
/* A track is a bigger decision than a course, and reads as one. */.choice.branch { border-left: 2px solid var(--lavender);}
.reset { margin-top: 8px; padding: 3px 8px; color: var(--overlay); font-size: 11px;}
.shortfall { margin: 0 0 8px; color: var(--yellow); font-size: 11px;}
/* Branch option text is a sentence, not a course title, so it wraps. */.candidate .label { flex: 1; min-width: 0;}
/* A met requirement is signalled on its heading, and nowhere else: the pool below it stays fully pickable, because wanting a second literature course is a real thing to want. */.choice.met { border-left: 2px solid var(--green);}
.choice.met h3 { color: var(--green);}
.guessed { color: var(--yellow);}
.candidate .lands { color: var(--sky); font-size: 11px;}
/* ---- map ---- */
/* The picture turns, so the control that turns it sits above the picture. */.board + .export,.credits + .export { margin-bottom: 12px;}
.board { overflow-x: auto; padding-bottom: 8px;}
.graph { display: block;}
.graph .term-label { fill: var(--overlay); font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase;}
.graph .edge { stroke: var(--surface); stroke-width: 1.5;}
/* Dimming rather than hiding: the shape of the whole plan stays readable while one chain is traced through it. */.graph .edge.dim { stroke: var(--surface); opacity: 0.25;}
.graph .node rect { fill: var(--mantle); stroke: var(--surface);}
.graph .node .code { fill: var(--text); font-size: 11px; font-weight: 500;}
.graph .node .sub { fill: var(--overlay); font-size: 9px;}
/* The course's own name. Dimmer than its code, because you scan for the code and read the name once you have found it. */.graph .node .name { fill: var(--subtext); font-size: 9px;}
.graph .node.done .name,.graph .node.transfer .name { fill: var(--overlay);}
.graph .node .name.why { fill: var(--red);}
.graph .node { cursor: default;}
.graph .node.dim { opacity: 0.3;}
/* History is context, not plan: present and readable, clearly behind you. */.graph .term-label.past { fill: var(--surface);}
.graph .node.done rect { fill: var(--crust); stroke: var(--surface);}
.graph .node.done .code { fill: var(--overlay);}
.graph .node.running rect { stroke: var(--sky);}
/* Credit that arrived rather than being earned: present, and visibly apart. */.graph .node.transfer rect { fill: var(--crust); stroke: var(--surface); stroke-dasharray: 3 3;}
.graph .node.transfer .code { fill: var(--overlay);}
/* ---- what an advisor changed by hand ---- */
/* * A substitution is the one line of a degree audit a human wrote on purpose, * so it reads as a sentence rather than as another badge in a row of badges. */.notes { margin: -6px 0 14px;}
.swap { margin: 0 0 5px; color: var(--subtext); font-size: 11px;}
.swap b { color: var(--green); font-weight: 500;}
.swap .prog-name { color: var(--text);}
.swap .carries { color: var(--yellow);}
/* ---- load dials ---- */
.dials { display: flex; flex-wrap: wrap; gap: 24px; margin-bottom: 14px;}
.dial { display: flex; align-items: center; gap: 8px; color: var(--subtext); font-size: 11px; text-transform: none; letter-spacing: 0;}
.dial-label { color: var(--overlay); font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase;}
.dial-value { min-width: 2.5em; color: var(--text); text-align: right;}
.export { padding: 4px 10px; font-size: 11px;}
/* A light term is a thing to fix with one more course, not a thing to hide. */.graph .term-label.short { fill: var(--yellow);}
/* Credits, not courses: a gate with no line drawn to it. */.graph .sub.standing { fill: var(--peach);}
/* A link the catalog implies and the requisite table never states: the two * halves of a sequence. Dashed, because it is a different kind of claim. */.graph .edge.sequence { stroke-dasharray: 3 3;}
/* Tracing a chain: what a course waits on, and what waits on it. * * Two colours rather than one highlight, because they are two different * answers. Peach is behind you — work to do before this. Sky is ahead — * work stuck behind this until it is done. */.graph .node.before rect { stroke: var(--peach);}
.graph .node.after rect { stroke: var(--sky);}
.graph .node.focus rect { stroke: var(--lavender); stroke-width: 2;}
.graph .edge.before { stroke: var(--peach);}
.graph .edge.after { stroke: var(--sky);}
/* A course being carried to another term, and the term it would land in. */
.graph .node.moved rect { stroke: var(--lavender);}
.graph .node.clashes rect { stroke: var(--red);}
.graph .node.dragging { cursor: grabbing;}
.graph .node.dragging rect { filter: drop-shadow(0 3px 6px rgb(0 0 0 / 45%));}
.graph .band { fill: var(--surface); opacity: 0.35; pointer-events: none;}
.graph .over-band { fill: none; stroke: var(--red); stroke-width: 1.5; stroke-dasharray: 6 4; pointer-events: none;}
.graph .term-label.over { fill: var(--red);}