/* Two themes. Everything downstream reads these custom properties and nothing
 * hardcodes a colour, which is what makes a second theme a palette swap rather
 * than a rewrite — keep it that way.
 *
 * The accent token is `--accent`, NOT `--gold`, because the two themes fill it
 * with different hues: dark keeps the original gold, light uses Guild Wars 2's
 * legendary purple. It was renamed when light went purple — a token called
 * `--gold` holding #4c139d is an invitation to "fix" it back.
 *
 * Three states, deliberately: an explicit choice stamps data-theme on <html> and
 * always wins; with no choice stored we follow the OS via prefers-color-scheme.
 * The dark values live on bare :root so a browser that supports neither still
 * gets a complete, readable palette.
 */
:root{
  --bg:#0e1116; --panel:#161b23; --panel-2:#1c222c; --line:#2a323e;
  --text:#e6eaf0; --muted:#98a3b3; --dim:#b9c4d4;
  --accent:#e2b866; --accent-dim:#8a6f3c; --done:#5ec27a; --warn:#e0a15c; --bad:#ff6b6b;
  --live:#63b3ed;
  --shadow:0 1px 0 rgba(255,255,255,.03) inset, 0 8px 24px rgba(0,0,0,.35);
  --glow:#1a2130;
  --barbg:#0f141b;
  --rowhover:#151c26;
  --btnhover:#242c38;
  --edge-done:rgba(94,194,122,.5);
  --edge-live:rgba(99,179,237,.45);
  --edge-warn:rgba(224,161,92,.45);
  --edge-daily:rgba(224,161,92,.45);
  --edge-accent:rgba(226,184,102,.35);
  --ring-live:rgba(99,179,237,.18);
}

/* Light. Not a washed-out inversion, and not picked by eye — the values were
   generated in OKLCH and then measured for WCAG contrast against all three
   surfaces they can land on (panel #fff, panel-2, and the page background).
   OKLCH rather than HSL because HSL lightness is a lie across hues: hsl(60,x,50%)
   is far brighter than hsl(240,x,50%), so matching numbers there still gives one
   colour that shouts and one that whispers.

   The rules the palette follows, so a new colour can be added consistently:
     1. Every signal hue is at least 28° from every other, measured on the OKLCH
        wheel. That is what stops two states reading as the same state.
     2. The semantic colours all sit at OKLCH L 0.50, so none of them carries more
        visual weight than another — they land between 5.2 and 5.9 contrast.
     3. Every border token sits at L 0.71 / C 0.115, for the same reason.
     4. The neutrals carry a whisper of the accent hue (290°) rather than the
        blue-grey 254° they used to. Greys tinted away from the accent are what
        made the page read as two palettes stapled together.

   The individual choices:
     --accent  #4c139d  is Guild Wars 2's own legendary rarity purple, and it is
             sourced rather than picked: the wiki's MediaWiki:Monobook.css (its
             light skin) sets `.rarity-legendary { color: #4C139D }`. Its dark
             skin uses #974eff instead, with the comment "dark purple has poor
             contrast" — which is exactly why this theme cannot share one value
             with the other. Measures 11.13 on panel / 10.16 on bg.
             It replaced an amber #b45309 (5.02 / 4.59) in 2026-08-15.
     --accent-dim #7c3aed  clears 3:1 comfortably (5.20 at worst), so focus rings
             and active borders are actually visible, and it reads as a lighter
             tone of the same hue so the progress-bar gradient is a progression
             rather than two unrelated colours.
     --live  #7f5d00 at hue 85°, an amber. It used to be a blue #0369a1, which was
             the right answer while the accent was gold — but the accent moved to
             violet at 293°, and blue at 243° is only 50° from that, so the "live
             now" highlight stopped reading as its own signal and started reading
             as a washed-out brand colour. Amber is 152° away: near-complementary,
             the largest separation any signal here has.
             A gold that clears 4.5:1 as TEXT is unavoidably dark — that is just
             where yellow lives on white — so the visible amber comes from
             --edge-live and --ring-live, which are borders and have no floor.
     --warn  #934f10 at 56°, sitting between --bad (27.5°) and --live (85°) with
             28.5° and 29° of clearance. Warm, because a caution should be, but it
             must not be mistaken for either of its neighbours.
     --bad   #ac312b keeps red's conventional hue but drops chroma from 0.19 to
             0.16. At 0.19 it was the most saturated thing on the page after the
             accent, which is too loud for a colour used on two error strings.
     --edge-daily #4eacdf is new. The daily-craft card used to borrow --edge-warn,
             which was fine when nothing else was amber; with --live now amber the
             two Plan-tab card borders would have collided. It takes the blue that
             --live vacated, so the three card types read as three hues: amber for
             an event, blue for a daily craft, violet for a trading-post buy.
             DARK keeps its old amber for this token, so dark is untouched.
   `--bg` is a tinted near-white rather than a flat grey. That was originally
   forced by the amber accent, which missed 4.5 against anything darker; the purple
   clears it with an enormous margin, so the constraint is gone — the tint stays
   because panels need something to lift off, not because it is still required.
   Panels stay pure white against that tinted page with a real shadow.

   Each theme complements its OWN accent, which is why --live is blue in dark and
   amber in light. That is deliberate, not drift: dark's accent is gold, so blue is
   its far side; light's accent is violet, so amber is. Do not "unify" them. */
@media (prefers-color-scheme: light){
  :root:not([data-theme="dark"]){
    --bg:#f4f4fa; --panel:#ffffff; --panel-2:#f8f8fd; --line:#d5d4e5;
    --text:#181626; --muted:#605e73; --dim:#403d54;
    --accent:#4c139d; --accent-dim:#7c3aed; --done:#0d7648; --warn:#934f10; --bad:#ac312b;
    --live:#7f5d00;
    --shadow:0 1px 2px rgba(24,22,38,.05), 0 10px 28px rgba(24,22,38,.10);
    --glow:#e4e2fb;
    --barbg:#f5f5fb;
    --rowhover:#efeefa;
    --btnhover:#ebe9f6;
    --edge-done:#5db784;
    --edge-live:#c29b44;
    --edge-warn:#d88e57;
    --edge-daily:#4eacdf;
    --edge-accent:#a493e3;
    --ring-live:rgba(194,155,68,.32);
  }
}
:root[data-theme="light"]{
  --bg:#f4f4fa; --panel:#ffffff; --panel-2:#f8f8fd; --line:#d5d4e5;
  --text:#181626; --muted:#605e73; --dim:#403d54;
  --accent:#4c139d; --accent-dim:#7c3aed; --done:#0d7648; --warn:#934f10; --bad:#ac312b;
  --live:#7f5d00;
  --shadow:0 1px 2px rgba(24,22,38,.05), 0 10px 28px rgba(24,22,38,.10);
  --glow:#e4e2fb;
  --barbg:#f5f5fb;
  --rowhover:#efeefa;
  --btnhover:#ebe9f6;
  --edge-done:#5db784;
  --edge-live:#c29b44;
  --edge-warn:#d88e57;
  --edge-daily:#4eacdf;
  --edge-accent:#a493e3;
  --ring-live:rgba(194,155,68,.32);
}

*{box-sizing:border-box}
html,body{margin:0;padding:0}
body{
  background:radial-gradient(1200px 600px at 50% -10%, var(--glow) 0%, var(--bg) 60%) no-repeat;
  background-attachment:fixed;
  color:var(--text);
  font:15px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,Roboto,Helvetica,Arial,sans-serif;
  -webkit-font-smoothing:antialiased; padding:24px 20px 90px;
}
.wrap{max-width:1040px;margin:0 auto}
a{color:var(--accent)}

.eyebrow{font-size:12px;letter-spacing:.18em;text-transform:uppercase;color:var(--accent);font-weight:600;margin:0 0 7px}
/* clamped rather than fixed: 34px would crowd a narrow phone at this weight */
h1{font-size:clamp(28px,3.2vw,34px);line-height:1.15;margin:0 0 4px;font-weight:650;letter-spacing:-.015em}
.lede{color:var(--accent);font-size:clamp(18px,1.7vw,20px);line-height:1.4;margin:10px 0 7px;font-weight:550}
.sub{color:var(--muted);font-size:15px;line-height:1.5;margin:0 0 20px;max-width:78ch}

/* Masthead: the nav gets a row to itself at the top right, then the title below.
   `order` rather than DOM order, so the h1 still comes first in the markup. */
.masthead{display:flex;flex-direction:column;gap:16px;padding:4px 0 6px}
/* Title left, the game's logo in the space at the right. */
.mh-title{display:flex;align-items:center;justify-content:space-between;gap:24px;min-width:0}
.mh-name{min-width:0}
/* ArenaNet's logo. A background-image rather than an <img> so only the file the
   current theme needs is fetched — a hidden <img> is still downloaded. The
   three-state swap mirrors the palette exactly: the dark/white version is the
   base, and the two light selectors override it, so a browser that supports
   neither prefers-color-scheme nor data-theme still gets the right one. */
.gw2logo{
  flex:0 0 auto;width:124px;height:87px;
  background:url("gw2-logo-white.svg") no-repeat center/contain;
}
@media (prefers-color-scheme: light){
  :root:not([data-theme="dark"]) .gw2logo{background-image:url("gw2-logo.svg")}
}
:root[data-theme="light"] .gw2logo{background-image:url("gw2-logo.svg")}
@media (max-width:640px){
  .gw2logo{width:78px;height:55px}
}
/* About on the left, the third-party list pushed to the right. space-between
   keeps them visibly separate groups rather than one run of links. */
.mh-nav{order:-1;display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;
        gap:10px 24px;padding-bottom:12px;border-bottom:1px solid var(--line)}

.links{display:flex;gap:12px;flex-wrap:wrap;align-items:baseline;justify-content:flex-end;font-size:13px;
  position:relative}
.links a,.ours a{color:var(--dim);text-decoration:none;border-bottom:1px solid var(--line);padding-bottom:1px}
.links a:hover,.ours a:hover{color:var(--accent);border-bottom-color:var(--accent-dim)}
/* What each outbound link is, on hover.
   The bubble is positioned against the NAV, not against the link — `.links a` is
   not itself positioned, so the absolute ::after resolves against the relative
   nav above. That means every tooltip appears in the same place, reads as one
   caption line for whichever link you are pointing at, and cannot run off the
   side of the viewport the way a per-link bubble does at the ends of the row.
   :focus-visible is included so this is reachable by keyboard, not just a mouse. */
.links a[data-tip]:hover::after,.links a[data-tip]:focus-visible::after{
  content:attr(data-tip);position:absolute;top:calc(100% + 7px);right:0;z-index:5;
  max-width:min(430px,100%);padding:7px 11px;border-radius:9px;
  background:var(--panel);border:1px solid var(--line);box-shadow:var(--shadow);
  color:var(--dim);font-size:12px;line-height:1.5;font-weight:400;
  white-space:normal;text-align:left;pointer-events:none;
}
@media (prefers-reduced-motion:no-preference){
  .links a[data-tip]:hover::after,.links a[data-tip]:focus-visible::after{animation:tipin .12s ease-out}
}
@keyframes tipin{from{opacity:0;transform:translateY(-3px)}to{opacity:1;transform:none}}
.links .lt{color:var(--muted);font-size:11.5px;letter-spacing:.1em;text-transform:uppercase}
.ours{display:flex;align-items:baseline;gap:14px}
.ours a{color:var(--accent);border-bottom-color:var(--accent-dim);font-size:13px}
/* Reset the global button chrome — in the masthead this reads as a link, not a
   control, and a 38px pill would tower over the 13px links beside it. */
.themebtn{border:0;background:none;padding:0;font-size:13px;color:var(--dim);
  border-bottom:1px solid var(--line);border-radius:0;cursor:pointer;line-height:1.4}
.themebtn:hover:not(:disabled){background:none;color:var(--accent);border-color:var(--accent-dim)}
@media (max-width:760px){
  .mh-nav{justify-content:flex-start}
  .links{justify-content:flex-start}
}

/* Account bar — the key and the saved data, as one strip above the tabs.
   Sized to be as close to a single line as the content allows: the head is one
   row, and each section is an accordion that only one of can be open. */
.acctbar{background:var(--panel);border:1px solid var(--line);border-radius:12px;
  box-shadow:var(--shadow);margin:0 0 14px;overflow:hidden}
.abhead{display:flex;align-items:center;gap:12px;padding:8px 14px;min-width:0}
.abhead h2{margin:0;font-size:11.5px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--accent);flex:0 0 auto}
/* The account name sits right beside the heading it belongs to, and is the
   flexible cell — so it doubles as the spacer that pushes the buttons right,
   and still does when there is no name yet. */
.abhead .acct{font-size:12.5px;color:var(--dim);font-variant-numeric:tabular-nums;
  flex:0 1 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.abhead .status{margin:0;flex:1 1 auto;min-width:0;font-size:12px;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:right}
.abhead .ab{flex:0 0 auto;font-size:12px;border:1px solid transparent;border-radius:999px;
  padding:3px 10px;color:var(--muted)}
.abhead .ab::after{content:" ▸";font-size:9px}
.abhead .ab:hover:not(:disabled){background:var(--btnhover);color:var(--accent)}
.abhead .ab.on{border-color:var(--accent-dim);color:var(--accent);background:var(--panel-2)}
.abhead .ab.on::after{content:" ▾"}
.absec{display:none;padding:0 14px 13px;border-top:1px solid var(--line);margin-top:2px}
.absec.open{display:block}
.absec p.hint{margin:11px 0 10px;font-size:12.5px;color:var(--muted)}
.absec .status{margin-top:9px}
@media (max-width:640px){
  /* The status is the first thing to go: on a phone the head has room for the
     name and the two buttons, and the full text is one tap away inside. */
  .abhead .status{display:none}
  .abhead{flex-wrap:wrap;gap:8px}
  .abhead .acct{flex:1 1 auto}
}

/* nav */
.tabs{display:flex;gap:6px;flex-wrap:wrap;margin:0 0 18px;border-bottom:1px solid var(--line)}
.tabs button{
  border:0;border-bottom:2px solid transparent;background:none;color:var(--muted);
  padding:9px 14px;font-size:14px;cursor:pointer;border-radius:8px 8px 0 0;
}
.tabs button:hover{color:var(--text);background:var(--rowhover)}
.tabs button[aria-selected=true]{color:var(--accent);border-bottom-color:var(--accent)}
.tabs .count{font-size:11px;color:var(--muted);margin-left:6px}

.panel{background:var(--panel);border:1px solid var(--line);border-radius:13px;padding:16px 18px;margin-bottom:16px;box-shadow:var(--shadow)}
.panel h2{margin:0 0 4px;font-size:13px;letter-spacing:.14em;text-transform:uppercase;color:var(--accent)}
.panel p.hint{margin:0 0 12px;font-size:12.5px;color:var(--muted)}
.phead{display:flex;align-items:baseline;gap:10px}
.phead h2{flex:1}
.panel.folded p.hint,.panel.folded .row,.panel.folded #bosslist{display:none}
.panel.folded .status{margin-top:0}
#findPanel.folded .pills,#findPanel.folded .results{display:none}

.row{display:flex;gap:9px;flex-wrap:wrap;align-items:center}
input[type=text],input[type=password],input.search{
  flex:1 1 260px;min-width:0;height:38px;padding:0 12px;border-radius:9px;
  border:1px solid var(--line);background:var(--barbg);color:var(--text);font-size:14px;
}
input:focus{outline:2px solid var(--accent-dim);outline-offset:1px}
/* Masks the API key without making it a password field — see renderAccount(). */
input.masked{-webkit-text-security:disc;text-security:disc;font-family:monospace;letter-spacing:.08em}
button{
  border:1px solid var(--line);background:var(--panel-2);color:var(--text);
  padding:9px 15px;border-radius:9px;font-size:13.5px;cursor:pointer;white-space:nowrap;
  transition:background .15s ease,border-color .15s ease;
}
button:hover:not(:disabled){background:var(--btnhover);border-color:var(--accent-dim)}
button:disabled{opacity:.45;cursor:not-allowed}
button.primary{border-color:var(--accent-dim);color:var(--accent)}
button.link{border:0;background:none;color:var(--muted);font-size:12px;padding:2px 4px}
button.link:hover:not(:disabled){background:none;color:var(--accent)}
.status{margin-top:11px;font-size:12.5px;color:var(--muted);min-height:1.4em}
.status.err{color:var(--bad)}
.status.ok{color:var(--done)}

.pills{display:flex;gap:7px;flex-wrap:wrap;margin-top:11px}
.pill{background:var(--panel-2);border:1px solid var(--line);border-radius:999px;padding:4px 10px;font-size:11.5px;color:var(--muted);cursor:pointer}
.pill.on{border-color:var(--accent-dim);color:var(--accent)}
#goalopts{margin:0 0 13px}

/* catalog */
.results{margin-top:12px;max-height:330px;overflow:auto;border:1px solid var(--line);border-radius:10px;background:var(--barbg)}
.res{display:flex;align-items:center;gap:11px;padding:8px 12px;border-bottom:1px solid var(--line);cursor:pointer}
.res:last-child{border-bottom:0}
.res:hover{background:var(--rowhover)}
.res img{width:26px;height:26px;border-radius:5px;flex:0 0 auto;background:var(--panel-2)}
.res .rn{flex:1;font-size:13.5px}
.res .rt{font-size:11.5px;color:var(--muted)}
.tag{font-size:10.5px;padding:2px 7px;border-radius:999px;border:1px solid var(--line);color:var(--muted)}
.tag.owned{border-color:var(--edge-done);color:var(--done)}
.tag.tracked{border-color:var(--accent-dim);color:var(--accent)}
/* Pinned to Today by the curated table — shown whatever the filter decided, so
   it has to say why it is there or it reads as a stray row. */
.tag.pin{border-color:var(--edge-daily);color:var(--live)}
/* The expansion a meta is from — a quiet label, not a signal, so it takes the
   neutral border and muted text rather than any of the semantic colours. */
.tag.cat{border-color:var(--line);color:var(--muted)}
/* The wiki's one-line description of the event. */
.ev .blurb{margin:3px 0 0;font-size:12px;color:var(--muted);line-height:1.5}
/* Waypoint chat code. The button is the thing you press; the code beside it is
   there so it can still be selected by hand where the clipboard API is absent. */
.wpline{margin:6px 0 0;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.wpbtn{font-size:11.5px;padding:3px 9px;border-radius:999px;cursor:pointer;
  border:1px solid var(--line);background:var(--panel-2);color:var(--dim)}
.wpbtn:hover{border-color:var(--accent-dim);color:var(--accent)}
.wpcode{font-size:11px;color:var(--muted);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  user-select:all}
/* Pin and Mark-done stack on a narrow card and sit side by side otherwise. */
.evact{display:flex;flex-direction:column;gap:6px;align-items:stretch;flex:0 0 auto}
.pinbtn{cursor:pointer;font-size:13px;line-height:1;padding:4px 9px;border-radius:8px;
  border:1px solid var(--line);background:var(--panel-2);color:var(--muted)}
.pinbtn:hover{border-color:var(--accent-dim);color:var(--accent)}
.pinbtn.on{border-color:var(--accent-dim);color:var(--accent)}

.empty{padding:18px;text-align:center;color:var(--muted);font-size:13px}

/* goals */
.goal{background:var(--panel);border:1px solid var(--line);border-radius:12px;margin-bottom:11px;box-shadow:var(--shadow);overflow:hidden}
.goal.owned{border-color:var(--edge-done)}
.ghead{display:flex;align-items:center;gap:12px;padding:13px 16px;cursor:pointer}
.ghead img{width:34px;height:34px;border-radius:7px;background:var(--panel-2);flex:0 0 auto}
.ghead .gt{flex:1;min-width:0}
.ghead .gn{font-size:15px;font-weight:600;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ghead .gs{margin:2px 0 0;font-size:12px;color:var(--muted)}
.ghead .gp{font-size:13px;font-variant-numeric:tabular-nums;color:var(--muted)}
.ghead .x{border:0;background:none;color:var(--muted);font-size:17px;padding:2px 4px;line-height:1}
.ghead .x:hover{color:var(--bad);background:none}
.gbody{border-top:1px solid var(--line);padding:6px 0;display:none}
.goal.open .gbody{display:block}
.gnote{padding:10px 16px;font-size:12.5px;color:var(--muted)}

.mat{display:flex;align-items:center;gap:10px;padding:6px 16px;font-size:13px}
.mat .tw{width:11px;flex:0 0 auto;font-size:9px;color:var(--muted);line-height:1}
.mat.kids{cursor:pointer}
.mat.kids:hover{background:var(--rowhover)}
.mat.kids .mn{color:var(--dim)}
.mat img{width:22px;height:22px;border-radius:5px;background:var(--panel-2);flex:0 0 auto}
.mat .mn{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.mat .rar{font-size:10.5px;color:var(--muted);border:1px solid var(--line);border-radius:999px;padding:1px 6px;margin-left:5px}
.mat .mc{font-variant-numeric:tabular-nums;font-size:12.5px;color:var(--muted);flex:0 0 auto}
.mat.have .mc{color:var(--done)}
.mat .mbar{width:78px;height:5px;border-radius:999px;background:var(--barbg);border:1px solid var(--line);overflow:hidden;flex:0 0 auto}
.mat .mbar i{display:block;height:100%;background:linear-gradient(90deg,var(--accent-dim),var(--accent))}
.mat.have .mbar i{background:var(--done)}
/* what a leaf step opens into: where it comes from, not more steps */
.leafinfo{padding:4px 16px 10px;font-size:12.5px;color:var(--dim);line-height:1.55}
.leafinfo p{margin:4px 0}
.leafinfo .meta{font-size:11.5px;color:var(--muted)}
.leafinfo .cost{margin:6px 0;font-size:12px;line-height:1.6}
.leafinfo .cost b{color:var(--accent);font-weight:600}
.leafinfo .cost .where{color:var(--muted)}
.leafinfo .cost b .bal{color:var(--muted);font-weight:500;font-size:11px;font-variant-numeric:tabular-nums;white-space:nowrap}
.leafinfo .cost b .bal.ok{color:var(--done)}

.mat .pin{border:0;background:none;color:var(--muted);font-size:14px;padding:0 2px;line-height:1}
.mat .pin:hover,.mat .pin.on{color:var(--accent);background:none}

/* plan cards */
.cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:12px}
.card{background:var(--panel-2);border:1px solid var(--line);border-radius:11px;padding:12px 14px}
.card.evt{border-color:var(--edge-live)}
.card.daily{border-color:var(--edge-daily)}
.card.buy{border-color:var(--edge-accent)}
.card h3{margin:0 0 3px;font-size:14px;font-weight:600;display:flex;align-items:center;gap:8px}
.card h3 img{width:22px;height:22px;border-radius:5px;background:var(--panel-2)}
.card .qty{font-size:12px;color:var(--muted);font-variant-numeric:tabular-nums;margin-left:auto}
.card p{margin:6px 0 0;font-size:12.5px;color:var(--dim);line-height:1.5}
.card .meta{margin-top:8px;font-size:11.5px;color:var(--muted)}
.card .via{display:inline-block;margin-top:8px;font-size:11.5px;padding:3px 9px;border-radius:999px;border:1px solid var(--line);color:var(--muted)}
.card .via.live{border-color:var(--live);color:var(--live)}

.grouphead{display:flex;align-items:baseline;gap:10px;margin:20px 0 10px}
.grouphead h2{margin:0;font-size:13px;letter-spacing:.14em;text-transform:uppercase;color:var(--accent)}
.grouphead .n{font-size:12px;color:var(--muted)}
.grouphead .subtotal{font-size:12.5px;color:var(--accent);font-variant-numeric:tabular-nums}
.grouphead .subtotal .alt{color:var(--muted);font-size:11.5px}

/* Shopping list — the trading post half of a plan, as copyable text. */
.shop{margin-top:12px}
.shopbtn{font-size:13px}
.shopbox{display:none;margin-top:10px}
.shopbox.open{display:block}
.shopnote{font-size:12px;color:var(--done)}
.shoptext{width:100%;resize:vertical;padding:11px 13px;border-radius:10px;
  border:1px solid var(--line);background:var(--barbg);color:var(--dim);
  font:12.5px/1.6 ui-monospace,SFMono-Regular,Menlo,Consolas,"Liberation Mono",monospace;
  white-space:pre;overflow-x:auto}
.shoptext:focus{outline:2px solid var(--accent-dim);outline-offset:1px}
/* Inside a per-item goal the block is indented with the rest of the fold. */
.goal .gbody .shop{margin:12px 16px 0}

/* Plan sub-tabs: Global / Per item */
.subtabs{display:flex;gap:6px;margin:0 0 16px}
.subtabs button{
  border:1px solid var(--line);background:var(--panel-2);color:var(--muted);
  padding:6px 14px;border-radius:999px;font-size:12.5px;cursor:pointer;
}
.subtabs button:hover{color:var(--text)}
.subtabs button.on{border-color:var(--accent-dim);color:var(--accent);background:var(--panel)}
/* the per-item view reuses .goal, but its groups sit inside the fold */
.goal .gbody .grouphead{margin:14px 16px 8px}
.goal .gbody .cards{padding:0 16px 4px}
/* `.tp` is shared by the plan cards and the "Or buy it outright" block on a goal
   and an armory card, so it is scoped to both rather than duplicated. */
.card table.tp,.outright table.tp{width:100%;margin-top:7px;border-collapse:collapse;font-size:12px;font-variant-numeric:tabular-nums}
.card table.tp th,.outright table.tp th{text-align:right;font-weight:500;color:var(--muted);font-size:11px;padding:0 0 3px}
.card table.tp th:first-child,.outright table.tp th:first-child{text-align:left}
.card table.tp td,.outright table.tp td{padding:2px 0;color:var(--dim);text-align:right}
.card table.tp td:first-child,.outright table.tp td:first-child{text-align:left;color:var(--muted)}
.card table.tp tr.order td,.outright table.tp tr.order td{color:var(--done)}
.card table.tp tr.order b,.outright table.tp tr.order b{color:var(--done)}
/* a vendor that undercuts the trading post outright */
.card table.tp tr.vend td,.card table.tp tr.vend b{color:var(--accent)}
/* Its own bordered block: on a goal card it sits above a very long tree, and
   without an edge it reads as the first row of that tree rather than as the
   alternative to it. Max-width so a two-column table does not stretch to 1040px
   and put its numbers a screen away from their labels. */
.outright{margin:0 16px 14px;padding:10px 12px;max-width:340px;
  border:1px solid var(--line);border-radius:8px;background:var(--panel-2)}
.outright h4{margin:0;font-size:12px;letter-spacing:.04em;text-transform:uppercase;color:var(--muted)}
.outright .meta{margin:6px 0 0;font-size:11.5px;color:var(--muted);line-height:1.55}
.outright table.tp b{color:var(--text)}
/* Inside an armory card the panel already has its own inset, so it only needs
   the vertical rhythm. */
.armbody .outright,.arm .outright{margin:10px 0 0}
/* "Needed by 250 for Gift of Research → Aurene's Fang". The answer to the
   question a big number provokes, so it sits directly under the heading rather
   than below the price table. */
.card .forwhat{margin:5px 0 0;font-size:11.5px;color:var(--muted);line-height:1.5}
.card .forwhat b{color:var(--dim);font-weight:600}
.card .forwhat .towards{color:var(--accent)}
.card .cost{margin-top:8px;font-size:12px;color:var(--dim);line-height:1.6}
.card .cost b{color:var(--accent);font-weight:600}
.card .cost .where{color:var(--muted)}
/* Wallet balance beside a currency in a vendor cost. It sits inside .cost b, so
   it has to opt back out of the gold and weight that style the cost itself. */
.card .cost b .bal{color:var(--muted);font-weight:500;font-size:11px;font-variant-numeric:tabular-nums;white-space:nowrap}
.card .cost b .bal.ok{color:var(--done)}

/* Legendary Armory — the browse-and-read tab.
   Deliberately larger than the "Find a legendary" list on the Goals tab: that
   one is a picker you skim past, this one is meant to be read. */
.armfam{margin:18px 0 8px;font-size:12px;letter-spacing:.09em;text-transform:uppercase;
  color:var(--muted);display:flex;align-items:baseline;gap:8px}
.armfam .n{font-size:11px;color:var(--muted);border:1px solid var(--line);border-radius:999px;padding:0 7px;letter-spacing:0}
.armgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(430px,1fr));gap:12px;align-items:start}
.arm{background:var(--panel);border:1px solid var(--line);border-radius:12px;box-shadow:var(--shadow);overflow:hidden}
.arm.owned{border-color:var(--edge-done)}
.arm.open{grid-column:1/-1}
.armhead{display:flex;align-items:center;gap:13px;padding:13px 16px;cursor:pointer}
.armhead:hover{background:var(--rowhover)}
.armhead img{width:42px;height:42px;border-radius:8px;background:var(--panel-2);flex:0 0 auto}
.armt{flex:1;min-width:0}
.armn{margin:0;font-size:16px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.arms{margin:3px 0 0;font-size:12.5px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.armtags{display:flex;gap:5px;flex-wrap:wrap;justify-content:flex-end;flex:0 0 auto;max-width:46%}
.armtags .tag.rel{color:var(--muted)}
.armhead .tw{width:11px;flex:0 0 auto;font-size:10px;color:var(--muted)}
.armbody{display:none;border-top:1px solid var(--line);padding:12px 16px 15px}
.arm.open .armbody{display:block}
.armbody p{margin:8px 0 0;font-size:13.5px;line-height:1.6;color:var(--dim)}
.armbody p:first-child{margin-top:0}
.armbody .meta{font-size:12px;color:var(--muted)}
.armbody .flavor{color:var(--muted);font-style:italic}
.armbody h4{margin:16px 0 0;font-size:11.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--accent);font-weight:600}
.armbody .fxl{margin:9px 0 0;font-size:12px;color:var(--accent);font-weight:600}
.armbody ul{margin:3px 0 0;padding-left:19px;font-size:13px;line-height:1.6;color:var(--dim)}
.armbody blockquote{margin:7px 0 0;padding:8px 13px;border-left:2px solid var(--accent-dim);
  background:var(--panel-2);border-radius:0 8px 8px 0;font-size:13px;line-height:1.6;
  color:var(--dim);font-style:italic}
.chips{display:flex;flex-wrap:wrap;gap:6px;margin-top:11px}
.chip{font-size:11px;padding:3px 9px;border-radius:999px;border:1px solid var(--line);color:var(--muted);background:var(--panel-2)}
.armreq .cost{margin-top:8px;font-size:12.5px;color:var(--dim);line-height:1.6}
.armreq .cost b{color:var(--accent);font-weight:600}
.armreq .cost .where{color:var(--muted)}
.armreq .cost b .bal{color:var(--muted);font-weight:500;font-size:11px;font-variant-numeric:tabular-nums;white-space:nowrap}
.armreq .cost b .bal.ok{color:var(--done)}
.ingr{display:flex;flex-wrap:wrap;gap:7px;margin-top:7px}
.ing{display:flex;align-items:center;gap:7px;font-size:12.5px;color:var(--dim);
  background:var(--panel-2);border:1px solid var(--line);border-radius:9px;padding:5px 10px}
.ing img{width:20px;height:20px;border-radius:4px;background:var(--panel)}
.armact{display:flex;gap:9px;flex-wrap:wrap;align-items:center;margin-top:16px}
.wikibtn{font-size:12.5px;color:var(--muted);text-decoration:none;border-bottom:1px solid var(--line);padding-bottom:1px}
.wikibtn:hover{color:var(--accent);border-color:var(--accent-dim)}
.dimnote{color:var(--muted)}

/* Semantic text colours, and the one-off gap under the armory intro.
   These exist so app.js can stop writing `style="color:var(--done)"` into its
   HTML strings: an inline style ATTRIBUTE is the one thing a Content-Security-
   Policy hash cannot rescue, so eight of them were all that stood between this
   site and `style-src 'self'` with no `unsafe-inline`. A width that is genuinely
   dynamic (the material bar) is set through the CSSOM instead, which CSP does
   not treat as inline style at all. */
.cwarn{color:var(--warn)}
.cdone{color:var(--done)}
.cacc{color:var(--accent)}
.hint.gap{margin-top:12px}
@media (max-width:640px){
  .armgrid{grid-template-columns:1fr}
  .armtags{max-width:none}
}

/* collection requirements — the real cost behind a "buy it from X" hint.
   Shared by the plan card and the tree leaf, so it styles off .collreq alone
   rather than off either parent. */
.collreq{margin-top:8px}
.collreq .meta{margin:6px 0 0;font-size:11.5px;color:var(--muted);line-height:1.5}
.collreq .meta b{color:var(--accent);font-weight:600}
.creqbtn{margin:4px 0 0;padding:2px 0;font-size:11.5px;color:var(--accent)}
.creqbtn:hover:not(:disabled){color:var(--accent);text-decoration:underline}
.creqbody{display:none;margin-top:4px}
.creqbody.open{display:block}
.creqlist{margin:6px 0 0;border:1px solid var(--line);border-radius:9px;background:var(--barbg);
  max-height:290px;overflow:auto}
.creqrow{display:flex;align-items:center;gap:9px;padding:5px 9px;border-bottom:1px solid var(--line);font-size:12px}
.creqrow:last-child{border-bottom:0}
.creqrow img{width:20px;height:20px;border-radius:4px;background:var(--panel-2);flex:0 0 auto}
.creqrow .cq-n{flex:1;min-width:0;color:var(--dim)}
.creqrow .cq-h{display:block;font-size:10.5px;color:var(--muted);line-height:1.4}
.creqrow .cq-c{flex:0 0 auto;font-size:11px;color:var(--muted);font-variant-numeric:tabular-nums;white-space:nowrap}
.creqrow.has .cq-c{color:var(--done)}
.creqrow.has .cq-n{color:var(--text)}

/* events */
.ev{display:flex;align-items:center;gap:14px;padding:13px 15px;border:1px solid var(--line);border-radius:12px;background:var(--panel);margin-bottom:10px}
.ev.live{border-color:var(--live);box-shadow:0 0 0 1px var(--ring-live)}
.ev.done{opacity:.55}
.ev .et{flex:1;min-width:0}
.ev .en{font-size:15px;font-weight:600;margin:0}
.ev .em{margin:2px 0 0;font-size:12px;color:var(--muted)}
.ev .clock{font-variant-numeric:tabular-nums;font-size:19px;min-width:96px;text-align:right;color:var(--dim)}
.ev.live .clock{color:var(--live)}
.ev .gives{margin:6px 0 0;font-size:12px;color:var(--dim)}
.ev .gives b{color:var(--accent);font-weight:600}
/* how far along you are on what this meta gives */
.ev .gives .prog{font-variant-numeric:tabular-nums;font-size:11.5px;color:var(--muted);
  border:1px solid var(--line);border-radius:999px;padding:1px 7px;margin-left:4px}
.ev .gives .prog.ok{color:var(--done);border-color:var(--edge-done)}

/* currencies & weekly */
.panel.folded #curlist,.panel.folded .wk,.panel.folded .ev,.panel.folded .gnote{display:none}
.wk{margin:10px 0;font-size:12.5px;color:var(--dim);padding:8px 10px;border:1px solid var(--line);border-radius:9px;background:var(--panel-2)}
.wk b{color:var(--accent)}
.wk .prog{font-variant-numeric:tabular-nums;border:1px solid var(--line);border-radius:999px;padding:1px 8px;margin:0 2px}
.wk .prog.ok{color:var(--done);border-color:var(--edge-done)}
.cur{display:flex;align-items:center;gap:10px;padding:6px 2px;border-bottom:1px solid var(--line);font-size:13px}
.cur:last-child{border-bottom:0}
.cur.want{background:var(--rowhover)}
.cur.head{font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);border-bottom:1px solid var(--line);padding-bottom:5px}
.cur.head .cn{padding-left:30px}
.cur img{width:20px;height:20px;border-radius:4px;background:var(--panel-2);flex:0 0 auto}
.cur .cn{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cur .cn .capwiki{font-size:10px;border:1px solid var(--line);border-radius:999px;padding:0 5px;
  text-decoration:none;color:var(--muted);margin-left:5px}
.cur .cn .capwiki:hover{color:var(--accent);border-color:var(--accent-dim)}
.cur .ch{font-variant-numeric:tabular-nums;color:var(--dim);flex:0 0 auto;min-width:104px;text-align:right}
.cur .ch.ok{color:var(--done)}
.cur .ch .cneed{color:var(--muted);font-size:11.5px}
.cur .ccap{font-variant-numeric:tabular-nums;color:var(--muted);font-size:11.5px;min-width:86px;text-align:right;flex:0 0 auto}
.cur .ccap .prog{border:1px solid var(--line);border-radius:999px;padding:1px 6px}
.cur .ccap .prog.ok{color:var(--done);border-color:var(--edge-done)}
.cur .cw{font-variant-numeric:tabular-nums;color:var(--muted);font-size:11.5px;min-width:62px;text-align:right;flex:0 0 auto}
@media (max-width:640px){
  .cur .ccap{display:none}
  .cur.head .ccap{display:none}
}
/* Every currency row opens, the way every tree row does — the twisty is the cue
   that "8,700" is a number you can ask about rather than one to take on faith. */
.cur.kids{cursor:pointer}
.cur.kids .cn::before{content:"▸";display:inline-block;width:13px;color:var(--muted);
  font-size:10px;transition:transform .12s}
.cur.kids.open .cn::before{transform:rotate(90deg)}
.cur.head .cn::before{content:none}
.curinfo{padding:10px 4px 14px 17px;border-bottom:1px solid var(--line);
  font-size:13px;line-height:1.6;color:var(--dim)}
.curinfo h4{margin:12px 0 6px;font-size:11px;letter-spacing:.06em;text-transform:uppercase;
  color:var(--muted);font-weight:600}
.curinfo h4:first-child{margin-top:0}
.curinfo p{margin:0 0 7px}
.curinfo .meta{font-size:11.5px;color:var(--muted)}
/* ArenaNet's own wallet line, set apart from the wiki's summary above it —
   they frequently say different things and must not blur together. */
.curinfo .ingame{color:var(--muted);font-size:12.5px}
.curinfo ul{margin:0 0 7px;padding-left:18px}
.curinfo li{margin:0 0 3px}
.curinfo a{color:var(--accent)}
/* The working behind the headline. Tabular numerals so the four columns line up
   as figures rather than as text. */
.whytab{width:100%;border-collapse:collapse;font-size:12px;font-variant-numeric:tabular-nums;
  margin:0 0 8px;max-width:560px}
.whytab th{text-align:right;font-weight:500;color:var(--muted);font-size:10.5px;
  letter-spacing:.03em;padding:0 0 4px;white-space:nowrap}
.whytab th:first-child{text-align:left}
.whytab td{padding:3px 0 3px 8px;text-align:right;color:var(--dim);border-top:1px solid var(--line)}
.whytab td:first-child{text-align:left;padding-left:0;color:var(--text)}
.whytab td img{width:16px;height:16px;border-radius:3px;vertical-align:-3px;margin-right:6px}
.whytab .vend{color:var(--muted);font-size:11px}
.whytab tr.more td{color:var(--muted);font-style:italic}
.whytab b{color:var(--accent);font-weight:600}

/* Readable prose, for about.html. The panels' own `p.hint` is 12.5px muted —
   right for a caption under a control, too small and too quiet for paragraphs
   somebody is meant to actually read.

   The line length is capped on the COLUMN, not on the paragraph. Capping the
   paragraph is the obvious move and it looks wrong: on a wide monitor the card
   stays 1040px wide while the text stops two thirds of the way across, so every
   card carries a strip of dead space down its right side. Narrowing the whole
   `doc` column instead keeps the same comfortable measure (79 characters,
   measured) and puts the leftover space outside the cards, where it reads as page
   margin. The masthead narrows with it so the header and the content share one
   edge — 1040px of header above 620px of card is its own kind of wrong. */
.wrap.doc{max-width:620px}
.prose{margin:0 0 12px;font-size:15px;line-height:1.7;color:var(--dim)}
.prose:last-child{margin-bottom:0}
.prose b{color:var(--text);font-weight:600}
.sig{margin:14px 0 0;font-size:13px;color:var(--muted)}
.sig b{color:var(--accent);font-weight:600}
/* Calls to action, styled as links that look clickable rather than as buttons —
   this is a thank-you, not a checkout. `ctas` wraps rather than squeezing, so
   the pair still reads as a pair on a phone. */
.cta{display:inline-block;padding:8px 15px;border-radius:999px;
  border:1px solid var(--accent-dim);color:var(--accent);text-decoration:none;font-size:13.5px}
.cta:hover{background:var(--btnhover)}
/* The second one is the author's own site rather than the thank-you, so it takes
   the quieter border: two identical pills would read as two equal asks. */
.cta.alt{border-color:var(--line);color:var(--dim)}
.cta.alt:hover{color:var(--text)}
.ctas{display:flex;flex-wrap:wrap;gap:9px;margin-top:14px}

.foot{margin-top:24px;font-size:12px;color:var(--muted);line-height:1.65}
.foot code{background:var(--panel-2);padding:1px 5px;border-radius:4px;font-size:11.5px}
.spin{display:inline-block;width:11px;height:11px;border:2px solid var(--line);border-top-color:var(--accent);border-radius:50%;animation:sp .7s linear infinite;vertical-align:-1px;margin-right:6px}
.spin.big{width:30px;height:30px;border-width:3px;margin:0;vertical-align:middle;flex:0 0 auto}
@keyframes sp{to{transform:rotate(360deg)}}

/* ---- loading, made obvious -------------------------------------------------
   The site's slow moments are an account sync and the recipe-tree walk, both of
   which can run for tens of seconds. They used to be announced by an 11px
   spinner, which is indistinguishable from a page that has simply stopped. Two
   cues now, and both are driven by the real request count from api.js rather
   than by a timer, so they cannot lie about whether anything is happening. */

/* 1. A bar across the very top of the viewport. */
.loadbar{position:fixed;top:0;left:0;right:0;height:4px;z-index:60;pointer-events:none;
  overflow:hidden;opacity:0;transition:opacity .25s ease;background:var(--barbg)}
.loadbar.on{opacity:1}
.loadbar::before{content:"";position:absolute;inset:0;width:38%;
  background:linear-gradient(90deg,transparent,var(--accent-dim),var(--accent),var(--accent-dim),transparent);
  animation:sweep 1.15s linear infinite}
@keyframes sweep{from{transform:translateX(-100%)}to{transform:translateX(365%)}}
/* Motion is the whole signal here, so reduced-motion gets a solid lit bar rather
   than nothing at all. */
@media (prefers-reduced-motion:reduce){
  .loadbar::before{animation:none;width:100%;background:var(--accent)}
  .spin{animation-duration:2.4s}
}

/* 2. A panel that says, in words, what is being waited on. */
.panel.busy{border-color:var(--accent-dim)}
.busyrow{display:flex;align-items:center;gap:16px}
.busytitle{margin:0;font-size:16px;font-weight:600;color:var(--text)}
.busynote{margin:3px 0 0;font-size:13px;color:var(--muted);line-height:1.55}
.busycount{font-variant-numeric:tabular-nums;color:var(--accent);font-weight:600}
/* The indeterminate track under the panel copy: it is honestly indeterminate,
   because the tree walk discovers how much work there is as it goes. */
.busytrack{margin-top:12px;height:6px;border-radius:999px;background:var(--barbg);
  border:1px solid var(--line);overflow:hidden;position:relative}
.busytrack::before{content:"";position:absolute;top:0;bottom:0;width:32%;border-radius:999px;
  background:linear-gradient(90deg,var(--accent-dim),var(--accent));animation:sweep 1.4s ease-in-out infinite}
@media (prefers-reduced-motion:reduce){ .busytrack::before{animation:none;width:100%} }
.hide{display:none !important}
@media (max-width:640px){
  body{padding:16px 12px 70px}
  .cards{grid-template-columns:1fr}
  .ev .clock{font-size:16px;min-width:76px}
}

/* ---- the static per-legendary pages ----------------------------------------
   /legendary/*.html, generated by tools/build-pages.py. Those pages carry no app
   code at all, so nothing whose visibility depends on a JS-toggled class can be
   reused: `.armbody` is display:none until `.arm` gains `.open`. This restates
   that typography for a plain page body and adds the two layouts the tracker has
   no equivalent of — the index grid, and a set page's per-piece block.

   Tokens only, no literal colours, per the palette rules at the top of this
   file: these pages follow the same theme as the tracker, including the
   pre-paint stamp, so a bare hex would look right in one theme and wrong in the
   other. Nothing below is used by the tracker, so it cannot regress it. */
.lp p{margin:9px 0 0;font-size:14.5px;line-height:1.7;color:var(--dim)}
.lp p:first-child{margin-top:0}
.lp .meta{font-size:12.5px;color:var(--muted)}
/* ArenaNet writes these with real line breaks — Twilight's names the three
   things it combines with, one per line. The tracker collapses them because a
   card is a card; a page is for reading, so it keeps them. */
.lp .flavor{color:var(--muted);font-style:italic;white-space:pre-line}
.lp h3{margin:0;font-size:17px;font-weight:600;letter-spacing:-.01em}
.lp h4{margin:20px 0 0;font-size:11.5px;letter-spacing:.13em;text-transform:uppercase;
  color:var(--accent);font-weight:600}
.lp .fxl{margin:10px 0 0;font-size:12px;color:var(--accent);font-weight:600}
.lp ul{margin:4px 0 0;padding-left:19px;font-size:13.5px;line-height:1.65;color:var(--dim)}
.lp blockquote{margin:9px 0 0;padding:8px 13px;border-left:2px solid var(--accent-dim);
  background:var(--panel-2);border-radius:0 8px 8px 0;font-size:13.5px;line-height:1.6;
  color:var(--dim);font-style:italic}
.lp .cost{margin-top:9px;font-size:12.5px;color:var(--dim);line-height:1.6}
.lp .cost b{color:var(--accent);font-weight:600}
.lp .cost .where{color:var(--muted)}
.lp .chips{margin-top:13px}
/* A class rather than an inline style attribute. The generated pages are the
   part of the site that could take a strict Content-Security-Policy tomorrow —
   they load no app code at all — and `style="..."` in the markup is the one
   thing a hash cannot rescue. app.js still has eight of them; those are the
   tracker's problem, not these pages'. */
.lp .wikiref{margin-top:16px}

/* The item's own portrait, beside its name on a page about one thing. */
/* The item's own icon sits in the masthead beside its name, not in the panel:
   the panel opens with the wiki's sentence about the thing, and a picture with
   the name repeated under it was the same name twice on one screen. */
.mh-name.lpid{display:flex;align-items:center;gap:15px}
.lpicon{width:52px;height:52px;border-radius:10px;background:var(--panel-2);flex:0 0 auto}

/* A set page lists every piece, because the pieces share a description but NOT a
   recipe — and the recipe is the reason the page is worth reading. */
.lppiece{border-top:1px solid var(--line);padding-top:16px;margin-top:18px}
.lppiece:first-child{border-top:0;padding-top:0;margin-top:0}
.lpname{display:flex;align-items:center;gap:11px;margin-bottom:4px}
.lpname img{width:32px;height:32px;border-radius:7px;background:var(--panel-2);flex:0 0 auto}
.lpname p{margin:2px 0 0}

/* The index of all 81 pages. */
.lpgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:10px}
.lpcard{display:flex;align-items:center;gap:11px;padding:10px 12px;border:1px solid var(--line);
  border-radius:10px;background:var(--panel-2);text-decoration:none;color:var(--text)}
.lpcard:hover{border-color:var(--accent-dim);background:var(--rowhover)}
.lpcard img{width:34px;height:34px;border-radius:7px;background:var(--panel);flex:0 0 auto}
.lpcard b{display:block;font-size:14px;font-weight:600;color:var(--text);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.lpcard>span{min-width:0}
.lpcard span span{display:block;font-size:11.5px;color:var(--muted);margin-top:2px}
