/*
 * Mobile first. The layout starts as a single column with a bottom tab bar,
 * and only becomes multi-panel once there is room. Committed dark: a passive
 * tree is a starfield, and every comparable tool commits the same way.
 */

/*
 * DIRECTION G, "The Instrument". The palette, not the markup.
 *
 * Every colour in this file already flowed through the ten names below, so the
 * direction lands by changing what they resolve to rather than by touching 44
 * border rules and 47 accent rules one at a time. Selectors are unchanged; the
 * ground they sit on is not.
 *
 * The ground loses its hue. It was violet grey (#201d25 / #2b2833 / #423d4c),
 * which put chroma in the chrome and left none for the game, so every rarity
 * had to shout over a coloured page. True neutral graphite gives it back:
 * anything with hue on this page now belongs to something the player earned.
 *
 * The accent is a cold cyan and not the old gold, for one reason that is worth
 * stating. Gold IS a rarity in this game. An accent that marks the current tab
 * in the same colour the item list uses for a rare item cannot be read as
 * either, and the previous palette used gold for the brand, the current tab,
 * the focus ring, allocated nodes and rare items at once. Cyan appears in no
 * ARPG rarity ladder, so state can never be mistaken for loot.
 */
:root {
	color-scheme: dark;

	--ground: #0B0C0D;
	--surface: #15181B;
	--surface-2: #1B1F23;
	--line: #262C33;
	--ink: #E9EDF0;
	--muted: #9AA4AD;
	/* State only: brand, current section, current tab, focus ring. */
	--accent: #38C8E0;
	--accent-dim: #0E3F4A;
	/* Route preview on the boards. Indigo rather than the old blue, so it never
	   reads as the cyan that means "you are here". */
	--path: #7C8CF8;
	--danger: #E14B39;
	/* --danger is a fill colour and is below the 4.5:1 body text needs on
	   --surface. This is the same hue lifted for text. */
	--danger-text: #F0907E;
	--total: #B166DC;
	/* Search highlight. Distinct from --path (route preview) and --accent
	   (allocated), because all three can be on screen at once. */
	--find: #E3C34A;

	/*
	 * The class hue, on the frame and nowhere else.
	 *
	 * All 24 classes carry one, set by the app when the class changes. It marks
	 * the mark, the hairline under the masthead and allocated sub-talents. It
	 * never marks state, which is the accent's job, and it never marks a value,
	 * which the rarity ladder's job. That boundary is what stops 24 hues
	 * colliding with the eight below.
	 */
	--class-h: 190;
	--class: hsl(var(--class-h) 52% 62%);
	--class-dim: hsl(var(--class-h) 40% 40%);
	--class-wash: hsl(var(--class-h) 60% 55% / .10);

	/* The rarity ladder. Data only, and the only place saturation is spent. */
	--r-normal: #C9D1D9;
	--r-magic: #5AA9FF;
	--r-rare: #E3C34A;
	--r-unique: #E28A2C;
	--r-mythic: #B166DC;
	--r-satanic: #E14B39;
	--r-angelic: #F2DFA0;
	--r-heroic: #55C77C;
	--r-unholy: #D14372;
	--r-rune: #C79A58;

	/* No serif. Both dark directions rejected it for the same reason: a Georgia
	   masthead over a data table is the costume every ARPG fan site wore in
	   2014, and it is most of what "dated" meant. */
	--font-display: "Segoe UI Variable Display", "Segoe UI", system-ui, -apple-system, sans-serif;
	--font-ui: "Segoe UI Variable Text", "Segoe UI", system-ui, -apple-system, sans-serif;
	--font-data: "Cascadia Mono", ui-monospace, "SF Mono", Consolas, monospace;

	/* 52, and it was 56. Three chrome rows stack above the planner on a desktop
	   and together they were 161px of the window before a single pixel of the
	   tool: the site owner's word for it was "one massive grey blob". Every one
	   of the three has come down, here and in the 1000px block at the foot of
	   this file, and the tallest thing inside this row is a 38px control, so 52
	   still leaves seven clear pixels above and below it. */
	--bar: 52px;
	/* The site navigation strip. Its own row rather than part of --bar, because
	   the bar already scrolls sideways on a phone and anything added to it is
	   pushed off the end, which is exactly how the reference pages came to be
	   invisible.

	   47 and not 44. The links stretch to the strip less its 1px rule, and the
	   audit measured them at 41 against a 42px row: three short of the 44x44 it
	   advises for a touch target, on the one row a phone user reaches for most.
	   This is the phone value and it is not the one that was costing height: on
	   a phone this strip and the masthead are the only chrome, since the tabs
	   are at the foot of the screen. The desktop value is in the 1000px block. */
	--nav: 47px;
	--tabs: 58px;
	--strip: 46px;
	/* The mobile anchor's row, and it is zero until something is actually in the
	   slot. It used to be 62px held open at all times so that a late creative
	   could not shove the page, which is the right instinct and the wrong trade
	   here: there is no ad network attached to this build, so what it reserved
	   was 62px of a phone screen showing a dashed empty box for ever. The 62 is
	   restored by the :has() rule in the advertising block, on the one condition
	   that makes it worth having, which is a slot with an ad in it. */
	--anchor: 0px;

	/* ---- the design's measure ----
	   One container, one gutter, two fixed columns, and every row on the site
	   agrees with them. These are the phone values; the 1000px block widens the
	   padding and the gutter to the design's 28, and the ladder at the foot of
	   this file narrows the Ledger and drops the pillar on the way down.

	   They live here rather than in the media query because pages.css and the
	   reference pages need the same four numbers, and a measure that is defined
	   twice is a measure that will be two different numbers by next week. */
	--max: 1600px;
	/* 28 is the site's page padding at every width above 760, on the planner and
	   on a reference page alike; the ladder at the foot of this file takes it to
	   16 on a phone. It was 16 here with 28 only inside the 1000px block, so at
	   900 the planner's wordmark sat at x=16 and every reference page's sat at
	   28: one site, two left edges, on exactly the widths a small laptop uses. */
	--pad: 28px;
	--gut: 16px;
	--pillar: 160px;
	--ledger: 344px;

	/* How far off the foot of the window the consent banner floats.

	   Everything stacked at the bottom of a phone screen, and nothing more: the
	   tab strip, the mobile ad anchor's row, and the summary strip above them.
	   The banner is a fixed overlay, so it cannot move the layout when it
	   appears, and this is what stops it covering the one row a phone visitor
	   navigates with. It reads --anchor rather than assuming zero because that
	   row comes back the moment a creative fills the slot.

	   The desktop value is in the 1000px block, where the tab strip moves up
	   under the header and there is nothing at the foot of the window to clear. */
	--consent-lift: calc(var(--tabs) + var(--strip) + var(--anchor));
}

* { box-sizing: border-box; }

/*
 * The scrollbar's width, reserved on a page that never scrolls.
 *
 * The planner is a 100dvh shell with its panes scrolling inside it, so the
 * document has no scrollbar; every reference page on the site does. Both centre
 * their content in the same 1600px container, and a container centred in
 * 1920 does not land where one centred in 1905 lands. The result was that the
 * wordmark, the navigation and the first line of content all shifted about
 * seven pixels sideways every time you moved between the tool and a document,
 * which is small enough that nobody can name it and large enough that everybody
 * sees it.
 *
 * Reserving the gutter here costs the planner fifteen pixels it was not using
 * and buys one vertical for the whole site.
 */
html { scrollbar-gutter: stable; }
html, body {
	margin: 0;
	height: 100%;
	overflow: hidden;
	background: var(--ground);
	color: var(--ink);
	font-family: var(--font-ui);
	/* 16 and 1.6, up from 15 and 1.5. The two light directions were judged the
	   most readable of the six and this is most of the reason: a tool that is
	   read for an hour at a time is not the place to save a pixel. */
	font-size: 16px;
	line-height: 1.6;
	-webkit-text-size-adjust: 100%;
}

/* The OS draws select popups, not the page, so without a declared scheme the
   class dropdown opens as a white sheet over a graphite app. */
option { background: var(--surface-2); color: var(--ink); }

/* Touch targets never below 44px, and no accidental text selection while
   dragging the tree. */
button, select, input { font: inherit; color: inherit; }
button { -webkit-tap-highlight-color: transparent; touch-action: manipulation; }

/* DOM order is bar, sitenav, tabs, panes, rail. On mobile the tabs belong at the
   bottom, so rows are assigned explicitly rather than left to source order. */
#app {
	display: grid;
	/* minmax(0, 1fr), not 1fr: a grid track sizes to its widest child by
	   default, so the horizontally scrolling top bar was stretching the whole
	   app past the viewport and pushing the last tab off screen. */
	grid-template-rows: var(--bar) var(--nav) minmax(0, 1fr) auto var(--anchor) var(--tabs);
	grid-template-columns: minmax(0, 1fr);
	height: 100dvh;
	width: 100%;
	max-width: 100%;
	overflow: hidden;
}
#bar, #sitenav, #panes, #tabs, #railPane { min-width: 0; }
#bar       { grid-row: 1; }
#sitenav   { grid-row: 2; }
#panes     { grid-row: 3; }
#summary   { grid-row: 4; }
#adAnchor  { grid-row: 5; }
#tabs      { grid-row: 6; }

/* ---------- top bar ---------- */

#bar {
	display: flex;
	align-items: center;
	gap: 10px;
	/* 16, and it was 12. The mark now starts the row and it was sitting almost
	   against the edge of the glass; the wider figure is picked up again at
	   1000px, where it lines the mark up with the first navigation link. */
	padding: 0 var(--pad);
	background: var(--surface);
	/* The hairline that ends the masthead. It was removed when the navigation
	   strip was added, so that the two rows would read as one header block, and
	   what they read as instead was one undifferentiated slab: same grey, same
	   height, nothing between them. A single line is enough to say that the
	   brand and the navigation are two different rows of the same header. */
	border-bottom: 1px solid var(--line);
	overflow-x: auto;
	scrollbar-width: none;
	/* Anchor for the class hairline below. */
	position: relative;
}
#bar::-webkit-scrollbar { display: none; }

/* The class hue, as a hairline under the masthead and nothing else.
   No image, no texture, no bloom, and it fades out before it reaches the
   controls so it never sits behind a number. */
#bar::after {
	content: "";
	position: absolute;
	left: 0; right: 0; bottom: -1px;
	height: 1px;
	background: linear-gradient(90deg, var(--class) 0%, var(--class-dim) 22%, transparent 58%);
	pointer-events: none;
}

/* Mark and wordmark travel together, with their own gap: #bar's 10px is the
   spacing between separate controls, and these two are one thing. */
.brandbox {
	display: flex;
	align-items: center;
	gap: 9px;
	flex: none;
}

/* The mark, drawn in index.html and sized here.
   No width or height attribute on the element itself, for the usual reason: an
   SVG with only a viewBox takes the size the stylesheet gives it, and one with
   baked attributes has to be overridden in two places. */
.logomark {
	width: 24px;
	height: 24px;
	flex: none;
	/* The class hue, not the accent. The mark is the one piece of chrome allowed
	   to say whose character is loaded; the accent means "you are here" and is
	   already carried by the current tab and the current section.

	   Stated here rather than in a second rule further down: a duplicate
	   `.logomark` later in the file is the same specificity and simply wins by
	   source order, which is exactly how the mark stayed cyan while --class-h
	   was changing correctly underneath it. */
	color: var(--class);
	transition: color .25s;
}

/* The wordmark is the page's h1: a document with no level-one heading gives a
   screen reader nothing to start from. It is not styled back down as far as it
   used to be. At 16px, with no mark beside it, it was indistinguishable from
   the labels on the controls next to it, and a masthead whose name is the same
   size as the word "Level" does not read as a masthead. 19px with a little
   tracking is a name; it is still well short of a 32px h1. */
#bar .brand {
	margin: 0;
	font-family: var(--font-display);
	font-size: 19px;
	/* 600 and not 400. The face is no longer a serif, and a sans wordmark at
	   regular weight beside a row of 14px control labels reads as another
	   label rather than as the name of the site. */
	font-weight: 600;
	letter-spacing: 0.005em;
	line-height: 1.15;
	white-space: nowrap;
}
/* Name over tagline, as one block, so the pair has a single baseline against
   the mark rather than two things loosely stacked. */
.brandtext { display: flex; flex-direction: column; justify-content: center; }
#bar .tagline {
	font-family: var(--font-display);
	font-size: 11px;
	font-weight: 600;
	letter-spacing: 0.15em;
	text-transform: uppercase;
	color: var(--muted);
	line-height: 1;
	white-space: nowrap;
}
@media (max-width: 700px) { #bar .tagline { display: none; } }
/* Below this there is no room for a wordmark next to the controls. */
/* Below this there is no room for a wordmark next to the controls, but it is
   the page's only h1: hiding it with display:none took the document's top-level
   heading away from screen readers on exactly the devices most likely to be
   using one. Hidden from the screen, kept in the tree. */
@media (max-width: 560px) {
	#bar .brand {
		position: absolute;
		width: 1px;
		height: 1px;
		margin: -1px;
		padding: 0;
		overflow: hidden;
		clip-path: inset(50%);
		white-space: nowrap;
	}
	#bar .ctl label, #bar .ctl .lbl { display: none; }

	/*
	 * Paying for the mark.
	 *
	 * This bar is a horizontal scroller and at 390px it is already full: it
	 * measured 395px of content in a 390px window before the mark was added, so
	 * "Reset" sat one pixel off the end. A 24px mark and the gap in front of it
	 * would have pushed it 47 off the end, and this is the row the reference
	 * links were once lost off the end of. The mark is worth having on a phone,
	 * where it is the only thing left saying whose site this is, so its width is
	 * found rather than borrowed.
	 *
	 * All three of these are free at this size. The padding goes back to what it
	 * was, since there is no wordmark here to give room to; the gap tightens by
	 * three; and the two flexible spacers are removed outright, which is where
	 * most of it comes from. They only push things apart when the bar has spare
	 * room, and at this width there is none: they were already collapsed to zero
	 * and contributing nothing but two gaps.
	 */
	#bar { gap: 7px; }
	#bar .spacer { display: none; }
	.logomark { width: 21px; height: 21px; }
	.brandbox { gap: 7px; }
}
#bar .spacer { flex: 1; }
/* Class, Level and Hero Level read as one group and sit in the middle, with a
   flexible spacer either side, so the bar is balanced rather than left-heavy. */
.ctlgroup { display: flex; align-items: center; gap: 10px; flex: none; }

/* [hidden] is display:none in the UA sheet, which a class selector outranks.
   Without this the Hero Level tag stayed on screen showing a bare 0. */
.ctl[hidden] { display: none; }

.ctl {
	display: flex;
	align-items: center;
	gap: 6px;
	flex: none;
}
.ctl label, .ctl .lbl {
	font-size: 10px;
	letter-spacing: 0.09em;
	text-transform: uppercase;
	color: var(--muted);
}
.ctl select, .ctl input {
	background: var(--ground);
	border: 1px solid var(--line);
	border-radius: 6px;
	padding: 7px 8px;
	font-size: 14px;
	min-height: 38px;
}
.ctl input { width: 62px; font-family: var(--font-data); text-align: center; }
/* A border tint is not a focus indicator: at 1.6:1 against the resting border
   it is invisible to most people and absent for anyone who cannot see colour.
   The ring is the indicator; the tint stays as a second signal. */
.ctl select:focus, .ctl input:focus { border-color: var(--accent-dim); }
.ctl select:focus-visible, .ctl input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* A read-out, not a control: same footprint as the inputs beside it so the bar
   stays even, but flat rather than raised. */
.tag {
	display: inline-flex;
	align-items: center;
	min-height: 38px;
	padding: 0 11px;
	border: 1px solid var(--line);
	border-radius: 6px;
	background: var(--ground);
	font-family: var(--font-data);
	font-variant-numeric: tabular-nums;
	font-size: 14px;
	color: var(--accent);
}
.tag.spare { border-style: dashed; }

.btn {
	background: var(--surface-2);
	border: 1px solid var(--line);
	border-radius: 6px;
	padding: 8px 12px;
	font-size: 13px;
	min-height: 38px;
	cursor: pointer;
	white-space: nowrap;
}
.btn:hover { border-color: var(--accent-dim); }
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.btn.done { border-color: var(--accent); color: var(--accent); }
/* The one that destroys something. --danger is a 4.21:1 fill on --surface, which
   is why the text uses --danger-text and only the border is the fill colour. */
.btn.danger { border-color: var(--danger); color: var(--danger-text); }
.btn.danger:hover { border-color: var(--danger-text); }
.btn.danger:focus-visible { outline-color: var(--danger-text); }

/* ---------- links ---------- */

/*
 * There was no anchor anywhere in this app until the reference pages existed,
 * so there was nothing here to style and the UA's blue-and-underlined default
 * would have been the result of the first one added. These are the same three
 * rules privacy.html and pages.css use, so a link looks the same wherever a
 * visitor meets it.
 */
a { color: var(--accent); text-underline-offset: 2px; }
a:hover { color: var(--ink); }
a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }

/* The links inside a pane's footnote, which point at the reference pages.
   .note is 11.5px text, so a bare link there is a 15px-tall tap target and the
   accessibility audit fails anything under 24. Vertical padding on an inline
   element does not move the line it sits in, but it is part of the element's
   box and therefore part of what a finger can hit, so this is a real target
   and not a number massaged past a threshold. */
.note a { padding: 5px 2px; }

/* ---------- site navigation ---------- */

/*
 * The strip that says which of the site's three places you are in.
 *
 * This was two 13px text links at the end of #bar, and #bar scrolls sideways on
 * a phone, so on the device most visitors arrive on they were off the end of it.
 * A row of its own cannot be scrolled away, and it sits directly under the
 * wordmark where a site's navigation is looked for.
 *
 * It shares #bar's surface with no hairline between them, so the two rows read
 * as one header block; the single rule under the strip is what separates the
 * chrome from the app.
 *
 * The current section is marked twice over, in colour and with a 2px underline,
 * for the same reason #tabs marks its own: colour alone is not a state anyone
 * can rely on. The underline sits at the bottom here and at the top there, which
 * is simply where the edge of each strip is.
 *
 * The rules below are duplicated in pages.css, deliberately. The two stylesheets
 * are never loaded by the same document (see the note at the head of pages.css)
 * and the whole job of this strip is to look identical on both sides of the
 * site, so it is written out twice rather than shared and allowed to drift.
 */
#sitenav {
	display: flex;
	align-items: stretch;
	gap: 2px;
	/* Less 11px, so the first link's TEXT lands on the page's left edge rather
	   than its hit area doing so. The link keeps its padded 44px target. */
	padding: 0 calc(var(--pad) - 11px);
	background: var(--surface);
	border-bottom: 1px solid var(--line);
	overflow-x: auto;
	scrollbar-width: none;
}
#sitenav::-webkit-scrollbar { display: none; }

/* Height from the strip, not from padding: the accessibility audit fails an
   interactive target under 24 by 24 CSS pixels, and bare 13.5px text is about
   eighteen. Stretching to the row also means the underline lands on the strip's
   own edge rather than floating inside it. */
#sitenav a {
	display: inline-flex;
	align-items: center;
	padding: 0 11px;
	border-bottom: 2px solid transparent;
	color: var(--muted);
	font-size: 13.5px;
	text-decoration: none;
	white-space: nowrap;
}
#sitenav a:hover { color: var(--ink); background: var(--surface-2); }
#sitenav a[aria-current='page'] { color: var(--accent); border-bottom-color: var(--accent); }
/* Inset, because the strip has no room under the text for the default 2px ring
   and the outline would be clipped by the row. */
#sitenav a:focus-visible { outline: 2px solid var(--accent); outline-offset: -3px; border-radius: 4px; }

/* The planner is a tool and the two after it are documents. Three identical
   pills in a row would say they are the same kind of thing, which is the
   confusion this whole strip exists to end. */
.navsep {
	flex: none;
	align-self: center;
	width: 1px;
	height: 18px;
	margin: 0 7px;
	background: var(--line);
}
.navlbl {
	display: inline-flex;
	align-items: center;
	padding-right: 4px;
	color: var(--muted);
	font-size: 9.5px;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	white-space: nowrap;
	/* 0.75 measured 3.6:1 on --surface. This is a label, and a label is text. */
	opacity: 0.9;
}
/* Below this the wordmark goes too, and the three links plus the divider are
   what has to fit across 360px. The tag is the first thing to give up its
   width: the divider still carries the split. */
@media (max-width: 560px) {
	.navlbl { display: none; }
	#sitenav a { padding: 0 9px; font-size: 13px; }
	.navsep { margin: 0 5px; }
}

/* ---------- advertising ---------- */

/*
 * Slots, not ads. Nothing here loads a network.
 *
 * What these used to be was reserved space: a box of exactly the right size,
 * dashed, labelled "Ad 160x600", held open at all times so that dropping a tag
 * in later could not move a pixel of the interface. Reserving first is the
 * right way to avoid layout shift and it is not what was wrong with them.
 *
 * What was wrong is that no tag is ever dropped in. There is no ad account on
 * this build, so the reservation was permanent and the thing every visitor
 * actually saw was three grey rectangles announcing advertising that does not
 * exist, one of them a 160 by 600 column down the left of the window. The site
 * owner's report was exactly that: hide the boxes if there are no ads.
 *
 * So the rule is inverted. A slot with nothing in it takes no space and draws
 * nothing at all, and the whole apparatus of reserved dimensions switches on
 * with the data-filled attribute, at the moment there is a creative to protect
 * from shifting. Nothing is lost: the elements stay in the document, at their
 * declared sizes, and the day a tag renders it sets data-filled and gets the
 * layout that was designed for it.
 *
 * They are marked aria-hidden and are not focusable: an empty placeholder has
 * nothing to announce to a screen reader.
 */
/* One switch. Without data-ads on the root every slot collapses, so running the
   site ad-free costs nothing and needs no markup changes. */
:root:not([data-ads]) .ad { display: none !important; }

/* And the second switch: an empty slot is gone. !important because two of these
   slots are turned on by id inside a media query further down, and an id beats
   a class however the rule is written.

   The pillar is exempt, and it is the only one. The other two sit inside the
   reading column, so an empty one there is a hole in the middle of the content
   and hiding it costs nothing. The pillar sits in the margin, and the design
   gives it a column whether or not it is sold: dropping the track moves the
   workspace, the Ledger, the tab strip and every card in the doll 188px to the
   left, which is a different layout, not a tidier one. It draws a labelled
   dashed rectangle instead, so the reserved space explains itself. */
.ad:not([data-filled]):not(#adPillar) { display: none !important; }
/* The wrapper carries no frame of its own: the placeholder inside it has the
   dashed one, and a creative brings its own. A solid box around either is a
   second border on one object. */
#adPillar:not([data-filled]) { background: none; border: 0; }

/* The mobile anchor's row, restored only when the anchor is filled. :has() is
   the only way to say this in a stylesheet, since --anchor is read by #app's
   grid a long way up the tree from the slot it describes. */
:root:has(#adAnchor[data-filled]) { --anchor: 62px; }

/* What a filled slot looks like: a plain framed box the creative sits inside.
   No dashed border, because dashed said "placeholder" and there are no
   placeholders any more. */
.ad {
	display: grid;
	place-items: center;
	background: var(--surface);
	border: 1px solid var(--line);
	border-radius: 6px;
	overflow: hidden;
}
/* The "Ad 160x600" text. It is never drawn now, in either state: an empty slot
   is not rendered at all, and a filled one has a creative in it that does not
   need labelling by us. The rule stays because renderGear still writes the
   span into the in-content slot, and this file is where its layout lives. */
.adnote { display: none; }

#adAnchor {
	margin: 0 8px 6px;
	height: calc(var(--anchor) - 6px);
	border-bottom: none;
}
/* The rail unit only exists where the rail does, and the pillar only once the
   viewport is wide enough that 160px costs the planner nothing. */
#adRail, #adPillar { display: none; }

/* In-content unit, at the foot of the pane below everything else. It started
   between two sections, which put a hole in the middle of the equipment list;
   at the bottom it is passed on the way out rather than read through. */
.ad.inline {
	width: 100%;
	max-width: 300px;
	height: 250px;
	margin: 28px auto 4px;
}
@media (min-width: 760px) {
	.ad.inline { max-width: 728px; height: 90px; }
}

/*
 * Sprites.
 *
 * Every icon in the app is a window onto an atlas. The box is whatever the
 * stylesheet says it is; the sprite is drawn on ::before and fits inside it.
 * Nothing in JS knows or needs to know the box size, which is what stops a
 * stylesheet change from silently mis-scaling an icon.
 */
.ico { display: grid; place-items: center; overflow: hidden; }
.ico::before {
	content: '';
	display: block;
	width: var(--w, 0);
	height: var(--h, 0);
	aspect-ratio: var(--sw, 1) / var(--sh, 1);
	max-width: 100%;
	max-height: 100%;
	background-image: var(--img, none);
	background-size: var(--bw, auto) var(--bh, auto);
	background-position: var(--bx, 0) var(--by, 0);
	background-repeat: no-repeat;
	image-rendering: pixelated;
}

/* ---------- panes ---------- */

#panes { position: relative; overflow: hidden; }
.pane {
	position: absolute;
	inset: 0;
	overflow-y: auto;
	overflow-x: hidden;
	overscroll-behavior: contain;
	-webkit-overflow-scrolling: touch;
	display: none;
}
.pane.on { display: block; }
/* Layouts inside a pane must respond to the pane, not the window. The summary
   rail takes 340px and the ad pillar another 184px, so a 1024px window leaves
   the pane 684px: a viewport media query said "wide" and the two-column gear
   layout overflowed under the rail. A container query measures the box that
   actually holds the content. */
.pane { container-type: inline-size; container-name: pane; }
.pane.flush { overflow: hidden; }

/* ---------- the two unhappy paths ----------

   Both of these elements are built in JS and neither exists in index.html, so
   their rules used to be inline `style.cssText` strings in main.js. That was a
   consequence of who owned which file rather than a decision, and it put layout
   for the failure states in the one place nobody looks when the layout of the
   failure states is wrong. They live here now, beside the pane they cover. */

/* A pane that could not be drawn. An overlay rather than a rewrite of the
   pane's contents: the tree pane owns a canvas, a search field and a zoom
   cluster that the tree module holds references to, and replacing its innerHTML
   would destroy all three along with the "Try again" being offered. */
.paneFail {
	position: absolute;
	inset: 0;
	z-index: 7;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 12px;
	padding: 24px;
	text-align: center;
	background: var(--ground);
}
.paneFail .failRow { display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; }

/* What a damaged share link cost. A strip at the foot of the pane area, not a
   dialog: the visitor did nothing wrong and has nothing to decide, so it takes
   no focus and blocks nothing. Pointer events are off on the strip and on again
   only for the dismiss button, because the pane underneath is what they came
   for and a banner that eats clicks is worse than no banner.

   Above .paneFail: if both are on screen the link damage is still worth
   reading, and that overlay's controls are centred well clear of this. */
#hashNotice {
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	z-index: 8;
	display: flex;
	justify-content: center;
	padding: 0 10px 10px;
	pointer-events: none;
}
#hashNotice .noticeBox {
	pointer-events: auto;
	display: flex;
	align-items: center;
	gap: 12px;
	max-width: 560px;
	padding: 8px 10px;
	border: 1px solid var(--line);
	border-radius: 8px;
	background: var(--surface);
	color: var(--ink);
	font-size: 12.5px;
	line-height: 1.45;
	box-shadow: 0 8px 20px rgba(0, 0, 0, 0.5);
}

/* ---------- analytics consent ----------

   The banner that asks about analytics, shown only where consent is required.
   The markup is in index.html and the decision about who sees it is in main.js;
   this is only how it sits on the screen.

   Fixed, like #hashNotice above, and for the stronger version of the same
   reason. A banner in the grid would push the planner up by its own height at
   the moment it appeared, which is a layout shift on first paint on the one
   element a visitor has to read before they can do anything else. Out of flow it
   costs the layout nothing, and --consent-lift keeps it clear of the tab strip.

   pointer-events off on the positioner and on again for the box, so the strip of
   empty space either side of a 640px banner on a 1440px window is not a
   400px-wide dead zone over the pane underneath. */
#consent {
	position: fixed;
	left: 0;
	right: 0;
	bottom: calc(var(--consent-lift) + env(safe-area-inset-bottom, 0px));
	z-index: 20;
	display: flex;
	justify-content: center;
	padding: 0 10px 10px;
	pointer-events: none;
}
/* The attribute is how main.js shows and hides it, and `display: flex` above
   outranks the UA sheet's `[hidden] { display: none }`. Without this line the
   banner is on screen for every visitor in the world. */
#consent[hidden] { display: none; }

#consentBox {
	pointer-events: auto;
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 10px 14px;
	width: min(640px, 100%);
	padding: 12px 14px;
	border: 1px solid var(--line);
	border-radius: 10px;
	background: var(--surface);
	box-shadow: 0 10px 26px rgba(0, 0, 0, 0.55);
}
/* It takes focus when it appears so that a screen reader announces it and a
   keyboard lands on it, which means it can be the focused element and must
   therefore be able to show that it is. */
#consentBox:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* 280px of basis. Above that the text and the two buttons share one row; below
   it the row wraps and the buttons take a line of their own, which is what
   happens at 390px. */
.ctext { flex: 1 1 280px; min-width: 0; }

#consentTitle {
	display: block;
	margin-bottom: 2px;
	font-family: var(--font-display);
	font-size: 14px;
	font-weight: 400;
	color: var(--ink);
}
#consentText {
	margin: 0;
	font-size: 12.5px;
	line-height: 1.5;
	color: var(--muted);
}
/* The same trick as `.note a`: vertical padding on an inline element does not
   move the line it sits in, but it is part of what a finger can hit, and 12.5px
   text on its own is a 19px target against a 24px minimum. */
#consentText a { padding: 5px 2px; }

/*
 * Two answers of equal weight, and the equality is in the geometry rather than
 * in good intentions.
 *
 * `flex: 1 1 0` gives both buttons the same width whatever their labels say, so
 * "Reject" cannot end up narrower than "Accept" simply by being a shorter word,
 * and neither carries an accent fill or a heavier border. Reject is first in the
 * DOM, so it is also the first of the two the keyboard reaches.
 *
 * 42px rather than the 38px .btn asks for. This is the one control on the site a
 * visitor is required to use before anything else, on a phone, once.
 */
#consentRow {
	display: flex;
	gap: 8px;
	flex: 1 1 auto;
}
#consentRow .btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	flex: 1 1 0;
	min-width: 104px;
	min-height: 42px;
}

/*
 * The way back to the question, for a visitor who has already answered it.
 *
 * A button and not a link, because the answer is changed in place: an anchor
 * would navigate, and every URL on this site that is not the current one throws
 * away the fragment, which is where the whole build lives. It looks like a link
 * because that is what it behaves like from the keyboard and the mouse, and it
 * sits in `.note`, whose 11.5px text is why it carries the same padding `.note a`
 * does.
 */
.linklike {
	padding: 5px 2px;
	background: none;
	border: none;
	color: var(--accent);
	font: inherit;
	text-decoration: underline;
	text-underline-offset: 2px;
	cursor: pointer;
}
.linklike:hover { color: var(--ink); }
.linklike:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }

/* ---------- bottom tabs ---------- */

#tabs {
	display: grid;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	background: var(--surface);
	border-top: 1px solid var(--line);
	padding-bottom: env(safe-area-inset-bottom);
}
#tabs button {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 2px;
	background: none;
	border: none;
	border-top: 2px solid transparent;
	color: var(--muted);
	font-size: 11px;
	letter-spacing: 0.04em;
	cursor: pointer;
	min-height: 44px;
}
#tabs button .ti { width: 21px; height: 21px; }
#tabs button.on { color: var(--accent); border-top-color: var(--accent); }

/* Four labels, one of which is "Incarnation", have to survive a 360px phone.
   Below the second breakpoint the words are dropped rather than truncated,
   since a clipped word reads worse than an icon alone.

   Both thresholds are unchanged from when there were six tabs. They were
   measured against the widest label, "Incarnation" at 59.4px in the 11px size
   and 47.1px in the 9.5px one, and losing Runewords and Farming only widened
   every track: at 381px a quarter of the row is 95.3px rather than the 63.5px a
   sixth was, so the 9.5px size now fits the label with room over. The
   thresholds are kept anyway. They cost nothing at four tabs and they are what
   stops a fifth being added blind. */
@media (max-width: 520px) {
	#tabs button { font-size: 9.5px; letter-spacing: 0; }
	#tabs button .ti { width: 19px; height: 19px; }
}
@media (max-width: 380px) {
	#tabs button { font-size: 0; gap: 0; }
	#tabs button .ti { width: 22px; height: 22px; }
}

/* ---------- shared bits ---------- */

.label {
	font-size: 10px;
	letter-spacing: 0.11em;
	text-transform: uppercase;
	color: var(--muted);
	margin: 0 0 8px;
}
/* Equipment, Relics and Charms ran together as one wall of cells. Every
   heading after the first opens a new section, with a rule to land the eye. */
.label + .slots { margin-bottom: 4px; }
.slots + .label,
.note + .label {
	margin-top: 26px;
	padding-top: 16px;
	border-top: 1px solid var(--line);
}
.empty { color: var(--muted); font-style: italic; font-size: 14px; }
.pad { padding: 16px 14px 28px; }

.row2 {
	display: flex;
	justify-content: space-between;
	gap: 12px;
	padding: 5px 0;
	font-size: 14px;
	border-bottom: 1px solid rgba(51, 46, 63, 0.5);
}
.row2:last-child { border-bottom: none; }
.row2 .vals { display: flex; gap: 8px; white-space: nowrap; }
.row2 b {
	font-family: var(--font-data);
	font-variant-numeric: tabular-nums;
	font-weight: 500;
	color: var(--accent);
}
.row2 b.pct { color: var(--path); }
.row2 b.tot { color: var(--total); }
.src { font-size: 11px; color: var(--muted); }

/* ---------- class trees ---------- */

.spec-head {
	display: flex;
	align-items: baseline;
	gap: 10px;
	margin: 4px 0 10px;
}
.spec-head h2 {
	margin: 0;
	font-family: var(--font-display);
	font-size: 19px;
	font-weight: 400;
}
.spec-head .count { font-family: var(--font-data); font-size: 12px; color: var(--muted); }

.tier-label {
	font-family: var(--font-data);
	font-size: 10px;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--muted);
	padding: 12px 0 6px;
	border-top: 1px solid var(--line);
}
.tier-label.locked { color: var(--danger); }

.skills { display: grid; gap: 8px; grid-template-columns: minmax(0, 1fr); }

/* A prerequisite chain reads as one column: each skill sits below the one it
   needs, stepped in, with a rule down the left tying them together. */
.chain { display: grid; gap: 6px; margin-bottom: 14px; }
.chain .skill.sub { margin-left: calc(var(--d, 0) * 18px); position: relative; }
.chain .skill.sub::before {
	content: '';
	position: absolute;
	left: -11px; top: -9px; bottom: 50%;
	width: 10px;
	border-left: 1px solid var(--line);
	border-bottom: 1px solid var(--line);
	border-bottom-left-radius: 6px;
}
.skill .lv {
	margin-left: 7px;
	font-family: var(--font-data);
	font-size: 10px;
	color: var(--muted);
}
.rank.capped { color: var(--accent); }
.rank .bonus { margin-left: 5px; color: var(--path); }
.attr.capped .v { color: var(--accent); }
.attr button[disabled] { opacity: 0.35; cursor: not-allowed; }

.skill {
	display: flex;
	gap: 10px;
	align-items: center;
	padding: 8px 10px;
	background: var(--surface);
	border: 1px solid var(--line);
	border-radius: 8px;
	text-align: left;
	cursor: pointer;
	min-height: 56px;
}
.skill:hover { border-color: var(--accent-dim); }
.skill.taken { border-color: var(--accent); }
/* 0.45 faded the whole row to 4.14:1 on the name, 2.52:1 on the level and
   1.88:1 on the reason it is locked. Dimming a row is a legitimate way to say
   "not yet", but not at the price of the text that explains why. */
.skill[disabled] { opacity: 0.85; cursor: not-allowed; }
/* The row holds three controls, so the one that opens the detail is its own
   button rather than a handler on the row. Stripped back to inheriting
   everything, because visually it is still just the left of the row. */
.skinfo {
	display: flex;
	align-items: center;
	gap: 10px;
	flex: 1;
	min-width: 0;
	margin: 0;
	padding: 0;
	background: none;
	border: none;
	color: inherit;
	font: inherit;
	text-align: left;
	cursor: inherit;
}
.skinfo:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.skill .ico { width: 40px; height: 40px; flex: none; border-radius: 6px; }
.skill .body { flex: 1; min-width: 0; }
.skill .nm { font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.skill .rank { font-family: var(--font-data); font-size: 12px; color: var(--muted); }
.skill.taken .rank { color: var(--accent); }
/* --danger is 4.21:1 on this panel before the row is dimmed at all, so this
   line needs its own lighter red rather than a different opacity. */
.skill .why { font-size: 11px; color: var(--danger-text); }
.skill .step {
	flex: none;
	width: 34px; height: 34px;
	border-radius: 6px;
	background: var(--surface-2);
	border: 1px solid var(--line);
	font-size: 17px;
	line-height: 1;
	cursor: pointer;
}
/* Both steppers are now on every row in every state, so a rank that reaches the
   cap dims its plus instead of deleting it. Deleting it moved the minus 44 px to
   the right, into the exact pixel the plus had been under the pointer a moment
   earlier; the markup note in renderSkills has the measurements. Same 0.35 as
   the attribute steppers, so the two panes read as one control. */
.skill .step[disabled] { opacity: 0.35; cursor: not-allowed; }

/* ---------- incarnation board ---------- */

#board { display: block; width: 100%; height: 100%; touch-action: none; cursor: grab; }
#board:focus-visible { outline: 2px solid var(--accent); outline-offset: -3px; }
#board.drag { cursor: grabbing; }
#treeWrap { position: absolute; inset: 0; }

/* Search, floating over the top-left of the board. Deliberately opposite the
   zoom controls so neither ever sits under the other, at any width. */
#treeFind {
	position: absolute;
	inset: 10px auto auto 10px;
	z-index: 4;
	display: flex;
	align-items: center;
	gap: 4px;
	padding: 0 4px 0 9px;
	max-width: calc(100% - 20px);
	background: rgba(20, 19, 23, 0.94);
	border: 1px solid var(--line);
	border-radius: 9px;
	backdrop-filter: blur(3px);
}
#treeFind .fi { width: 15px; height: 15px; flex: none; color: var(--muted); }
#treeFind input {
	width: 190px;
	min-width: 0;
	padding: 9px 2px;
	background: none;
	border: none;
	font-size: 14px;
}
/* The blanket `outline: none` that used to sit in the rule above removed the
   only sign that Tab had landed here, and put nothing in its place. */
#treeFind input:focus { outline: none; }
#treeFind input:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
#treeFind input::placeholder { color: var(--muted); }
#treeFind input::-webkit-search-cancel-button { filter: invert(0.6); }
#findCount {
	flex: none;
	font-family: var(--font-data);
	font-variant-numeric: tabular-nums;
	font-size: 11px;
	color: var(--find);
	padding: 0 2px;
}
#findCount.none { color: var(--danger); }
#treeFind button {
	flex: none;
	width: 30px; height: 30px;
	border-radius: 6px;
	background: var(--surface-2);
	border: 1px solid var(--line);
	font-size: 13px;
	line-height: 1;
	cursor: pointer;
}
#treeFind button:disabled { opacity: 0.35; cursor: default; }
#treeFind button:not(:disabled):hover { border-color: var(--find); color: var(--find); }

/* On a phone the field takes the width it needs and the arrows stay reachable
   with a thumb. */
@media (max-width: 520px) {
	#treeFind { inset: 8px 8px auto 8px; }
	#treeFind input { width: 100%; flex: 1; }
}

/* The board switcher that used to sit here, `#boardPick`, is gone. It was a
   two-button toggle in the bottom left of the tree pane, and the site owner
   never found it: he reported the Ether board as missing from a build of the
   app that had shipped it for weeks. Incarnation and Ether are top-level tabs
   now, so the only control that chooses a board is the one in the tab strip
   with every other destination in the app. */

#treeHud {
	position: absolute;
	inset: auto 10px 10px auto;
	display: flex;
	gap: 6px;
}
#treeHud button {
	width: 40px; height: 40px;
	border-radius: 8px;
	background: rgba(30, 27, 40, 0.9);
	border: 1px solid var(--line);
	font-family: var(--font-data);
	font-size: 16px;
	cursor: pointer;
}

#tip {
	position: fixed;
	z-index: 20;
	max-width: min(300px, 82vw);
	padding: 10px 12px;
	background: rgba(20, 18, 28, 0.97);
	border: 1px solid var(--accent-dim);
	border-radius: 6px;
	box-shadow: 0 8px 24px rgba(0, 0, 0, 0.55);
	pointer-events: none;
	opacity: 0;
	transition: opacity 90ms ease;
}
#tip.on { opacity: 1; }
#tip h3 { margin: 0 0 5px; font-family: var(--font-display); font-size: 15px; font-weight: 400; }
#tip .tier { font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); margin-bottom: 6px; }
#tip .foot { margin-top: 8px; padding-top: 7px; border-top: 1px solid var(--line); font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em; }
#tip .foot.take { color: var(--path); }
#tip .foot.drop { color: var(--danger); }
#tip .foot.far { color: var(--muted); }
/* Item tooltips reuse the tree's tip box. They can be long, so they scroll
   rather than running off the screen. */
#tip .row2 { font-size: 12.5px; padding: 3px 0; }
#tip .row2 .src em { color: var(--danger); font-style: normal; font-size: 10px; }
#tip p.desc { font-size: 12px; margin: 0 0 8px; color: var(--muted); }
#tip { max-height: min(70vh, 520px); overflow-y: auto; }

/* ---------- equipment ---------- */

.slots { display: grid; gap: 8px; grid-template-columns: repeat(2, minmax(0, 1fr)); }

/*
 * The paper doll.
 *
 * A doll is a body, so the centre column is the body line and only the body
 * line: helmet, chest, belt, boots straight down it. The hands take the outer
 * columns and the jewellery fills the corners. The two empty cells are
 * deliberate; a doll with every cell filled is a table, and the silhouette is
 * what lets you find a slot without reading its label.
 *
 * Areas rather than auto-flow. Auto-flow fills reading order and knows nothing
 * about anatomy, which is how boots ended up in the top-left beside the helmet.
 *
 * Only above 700px, and only for the gear grid: the aux grids below (relics,
 * charms, sockets) share the .slots class and must keep flowing normally, so
 * the rule is scoped by :not(.aux).
 */
@container pane (min-width: 700px) {
	.slots:not(.aux) {
		grid-template-columns: repeat(3, minmax(0, 1fr));
		grid-template-areas:
			"amulet  helmet  ."
			"weapon_main chest offhand"
			"gloves  belt    ring_1"
			".       boots   ring_2";
	}
}
/* A slot and its sockets are one cell. Previously they were siblings of the
   grid, so a full-width socket strip landed under whichever row the slot
   happened to fall in rather than under the slot itself. */
/*
 * THE GEAR CARD, at the design's size, and why the frame is on the wrapper.
 *
 * The design draws one object: the art, the slot, the item, its base, and the
 * runes it holds, all inside a single border. The obvious way to build that is
 * to put the socket chips inside the clickable card, and it cannot be done: the
 * card is a control and so is every chip, and nesting one control inside
 * another is invalid markup, fails an accessibility audit outright, and in
 * practice routed a click on a rune to the picker for the whole piece.
 *
 * So the frame lives one level out. `.slotwrap` is the card the player sees:
 * the border, the surface, the corner and the rarity edge. `.slot` inside it is
 * transparent and is the region you click to change the item; the socket row is
 * its sibling, inside the same frame. One object to look at, two controls to
 * operate, and neither of them inside the other.
 *
 * 12px of padding, 12 between the art and the words, and a floor of 78 for the
 * clickable part. It was 8 and 9 and 58, which is a card built to be small
 * rather than one built to hold what it holds.
 *
 * The left edge is 2px and carries the rarity. A full border in the rarity
 * colour outlines the card and competes with the name inside it; an edge marks
 * it and stays out of the way, which is how the design draws the ladder on
 * every card in the app.
 */
.slotwrap {
	display: grid;
	align-content: start;
	min-width: 0;
	background: var(--surface);
	border: 1px solid var(--line);
	border-left: 2px solid var(--line-2, var(--line));
	border-radius: 10px;
	overflow: hidden;
}
.slotwrap[data-r] { border-left-color: var(--rr, var(--line)); }
.slotwrap:hover { border-color: var(--line-2, var(--muted)); }
.slotwrap:hover[data-r] { border-left-color: var(--rr, var(--line)); }
/* The socket row, inside the frame and indented to the text column: 12 of card
   padding plus the art's 44 and the 12 beside it. The chips read as a fourth
   line of the card rather than as a strip stuck under one. */
.slotwrap > .sockets { padding: 0 12px 12px 68px; margin: -4px 0 0; }
.slotwrap > .rwlist { padding: 0 12px 12px; }

/* The clickable part. No frame of its own: the wrapper has it. */
.slot {
	display: flex;
	gap: 12px;
	align-items: center;
	padding: 12px;
	background: none;
	border: 0;
	border-radius: 0;
	text-align: left;
	cursor: pointer;
	min-height: 78px;
}
/* A relic or a socket cell is a card in its own right, with nothing beside it
   in a wrapper, so it keeps the frame on itself. */
.slots.aux .slot, .slot.aux {
	background: var(--surface);
	border: 1px solid var(--line);
	border-left: 2px solid var(--line-2, var(--line));
	border-radius: 10px;
}
.slot.filled { border-color: var(--accent-dim); }
/* The slot carries role="button" so that it can be operated from the keyboard;
   that makes a focus ring mandatory rather than optional. */
.slot:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.cg.filled:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.slot .ico {
	width: 38px; height: 38px; flex: none;
	border-radius: 6px;
	background: var(--ground);
	border: 1px solid var(--line);
}


/* The client's slot art is a dark plate with a near-black silhouette, drawn to
   sit on the game's lighter inventory panel. On this ground it disappears, so
   it is lifted rather than dimmed; it still reads as a hint because a filled
   slot gets a full-strength icon and a lit border. */
.slot .ico.ph {
	background-color: transparent;
	filter: brightness(2.1) saturate(1.3);
	opacity: 0.8;
}
.slot:hover .ico.ph { opacity: 1; }
/* A grid panel, not a silhouette: it is already the right colour. */
.slot .ico.ph.plain { filter: none; opacity: 0.7; }
/* The card's three text lines, at the design's scale: a 10px uppercase kicker
   naming the slot, the item at 14.5 in its rarity colour, and the base at 12. */
.slot .body { min-width: 0; flex: 1; }
.slot .sn {
	font-family: var(--font-display);
	font-size: 10px;
	font-weight: 600;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--muted);
	line-height: 1.4;
}
.slot .it {
	font-size: 14.5px;
	font-weight: 500;
	line-height: 1.35;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.slot .it.none { color: var(--muted); font-style: italic; }
/* The socket chips, the card's fourth line. They wrap rather than clip: six on
   a weapon is a real case and a clipped runeword is unreadable. */
.slot .socks { display: flex; flex-wrap: wrap; gap: 3px; margin-top: 5px; }

/* Stacked by default. Two columns once the pane is wide enough that the charm
   grid is not squeezing the gear slots. */
.geartop { display: grid; gap: 18px; }
.gcol { min-width: 0; }

@container pane (min-width: 700px) {
	/*
	 * A fixed track, not auto, and aligned to the top rather than stretched.
	 *
	 * Matching the two columns' heights sounded right and was wrong in use: the
	 * charm grid took its height from the gear column, so equipping anything
	 * resized the whole grid and every cell in it. Measured, adding a socketed
	 * weapon and chest took it from 142px wide to 167 and the cells from 45 to
	 * 53. A board that reflows while you are working on the thing beside it is
	 * worse than one that does not line up.
	 *
	 * So the width is fixed and the height follows from the 3:11 aspect. The
	 * grid now only changes size when the viewport does.
	 */
	.geartop {
		grid-template-columns: minmax(0, 1fr) 212px;
		align-items: start;
		gap: 32px;
	}
	/* 212, the design's column. The board inside it is not stretched to fill it:
	   the cells are a fixed 50px, which is the whole point of the change. A
	   grid of minmax(0, 1fr) tracks divides whatever width it is given, so the
	   column width was setting the cell size and a 212px column produced 62px
	   cells that swallowed their own labels. Fixed cells and a fixed column are
	   two independent numbers, and the design gives both. */
	.charmcol { min-width: 0; }
	.charmcol .charmgrid { width: 212px; max-width: 212px; }
	/* No rule above the charm heading: it starts a column, not a section. */
	.charmcol .label { border-top: none; margin-top: 0; padding-top: 0; }
}

/* The charm grid is a real grid, three wide and eleven tall, and a charm spans
   the rectangle it occupies. Square cells so a 2x2 reads as a square. */
.charmgrid {
	display: grid;
	/*
	 * Fixed cells, and this is the difference between the board and every other
	 * grid in the app.
	 *
	 * It used to be minmax(0, 1fr) tracks under an aspect-ratio, so the cell
	 * size was whatever the column width divided by three happened to be. That
	 * is right for a layout grid and wrong for an inventory: a charm is a
	 * rectangle of cells, its art is drawn for a cell, and a cell that is 45px
	 * on one screen and 62 on another is a board whose contents change size for
	 * reasons that have nothing to do with the build. It also meant the column
	 * width and the cell size were the same number wearing two hats, so setting
	 * the column to the design's 212 silently made the cells 62.
	 *
	 * 50px cells and 3px gaps, from the design. Eleven rows of them is 583, and
	 * the panel is whatever that comes to.
	 */
	grid-template-columns: repeat(var(--cols), 50px);
	grid-auto-rows: 50px;
	gap: 3px;
	/* A backstop: nothing inside may make the grid bigger than its shape. */
	overflow: hidden;
}
.cg {
	display: grid;
	place-items: center;
	gap: 2px;
	min-width: 0;
	min-height: 0;
	background: var(--ground);
	border: 1px solid var(--line);
	border-radius: 6px;
	cursor: pointer;
	overflow: hidden;
}
.cg:hover { border-color: var(--accent-dim); }
.cg:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.cg.filled {
	grid-template-rows: minmax(0, 1fr) auto;
	background: var(--surface);
	border-color: var(--accent-dim);
	padding: 3px;
}
.cg.filled .ico { width: 100%; height: 100%; min-height: 0; }
/* The name only appears when the rectangle is big enough to hold it. */
.cg.filled .cn {
	font-size: 9px;
	line-height: 1.15;
	text-align: center;
	color: var(--muted);
	max-height: 2.3em;
	overflow: hidden;
}
.cg.locked {
	background: #0d0c0f;
	border-style: dashed;
	border-color: #322e3a;
	color: var(--muted);
	opacity: 0.85;
}
.cg.locked:hover { opacity: 1; border-color: var(--accent-dim); }
.cg.locked .lk { width: 13px; height: 13px; }

/* A charm that will not fit anywhere is shown, not hidden: knowing it exists
   and cannot be used is worth more than a shorter list. */
.pick.nofit { opacity: 0.45; cursor: not-allowed; }

/* Relics are smaller and more numerous than gear, so they get a denser grid. The charm grid keeps the game's three-wide shape at every width,
   because the three stat-gated cells sit at fixed positions in it. */
.slots.aux { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px; }
.slots.aux.charms { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.slot.aux { min-height: 46px; padding: 6px; gap: 7px; }
.slot.aux .ico { width: 34px; height: 34px; }
.slot.aux .it { font-size: 12px; text-transform: capitalize; }
/* A locked cell is an invitation, not a dead end: the player clicks it to say
   their character has the quest or node that opens it. So it stays clickable
   and lifts on hover rather than reading as disabled. */
/* Recessed rather than faded: a darker ground than the panels around it reads
   as a hole in the grid, which is what a locked cell is. Dimming with opacity
   alone left it the same depth as its neighbours. */
.slot.aux.locked {
	background: #0d0c0f;
	border-style: dashed;
	border-color: #322e3a;
	opacity: 0.85;
	cursor: pointer;
}
/* Was #6f6a77, which measured 3.00:1 through the cell's own 0.85 fade. */
.slot.aux.locked .it { color: #9a94a3; }
.slot.aux.locked .ico { opacity: 0.4; }
.slot.aux.locked:hover {
	opacity: 1;
	border-color: var(--accent-dim);
	background: #131118;
}
.slot.aux.locked:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.slot .it .lk { width: 12px; height: 12px; vertical-align: -2px; margin-right: 3px; }

/* Shown only on an unlocked-but-empty gated cell. Filling the cell hides it,
   because re-locking a filled cell would silently discard the item. */
/* Only present on a gated cell that has been opened and is still empty. The
   cell itself is a button, so this one cannot sit inside it; it is a sibling
   pinned to the corner instead. */
.auxwrap { position: relative; display: grid; min-width: 0; }
.auxwrap .relock { position: absolute; top: 4px; right: 4px; }

.relock {
	flex: none;
	width: 24px; height: 24px;
	display: grid; place-items: center;
	padding: 0;
	background: none;
	border: 1px solid transparent;
	border-radius: 5px;
	color: var(--muted);
	cursor: pointer;
}
.relock .lk { width: 13px; height: 13px; }
.relock:hover { color: var(--ink); border-color: var(--line); }
.sockets {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 6px;
	margin: -2px 0 2px 8px;
}
.sockets .slot.aux { min-height: 38px; padding: 4px 7px 4px 4px; flex: 0 1 auto; }
.sockets .slot.aux .ico { width: 28px; height: 28px; }
.sockets .slot.aux .it { font-size: 11px; }
.sockset {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	padding: 3px 6px;
	border: 1px dashed var(--line);
	border-radius: 6px;
}
.sockset b { font-family: var(--font-data); min-width: 12px; text-align: center; }
.sockset .step { width: 26px; height: 26px; font-size: 15px; }

.rwbadge {
	font-size: 11px;
	letter-spacing: 0.04em;
	padding: 3px 9px;
	border-radius: 5px;
	border: 1px solid var(--accent);
	color: var(--accent);
	background: rgba(217, 164, 65, 0.08);
	white-space: nowrap;
}
.rwhint {
	font-size: 10px;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	padding: 4px 8px;
	border-radius: 5px;
	border: 1px dashed var(--line);
	background: none;
	color: var(--muted);
	cursor: pointer;
	white-space: nowrap;
}
.rwhint:hover { border-color: var(--accent-dim); color: var(--ink); }
.rwlist {
	margin: 2px 0 6px 8px;
	padding: 6px 12px;
	background: var(--ground);
	border: 1px solid var(--line);
	border-radius: 8px;
	max-height: 300px;
	overflow-y: auto;
}

.sockhint { font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }

.label .sub2 { color: var(--muted); font-weight: 400; text-transform: none; letter-spacing: 0; }

/* ---------- confirming something that cannot be taken back ----------

   Same idiom as the picker below, one step higher, because a question about
   throwing a build away can be asked while the picker is open. Created and
   removed by confirmAction rather than living in the markup: a dialog that is
   only ever seen twice should not be in the document the other 99% of the time,
   where it is one missing `display: none` away from covering the app. */
#confirm {
	position: fixed;
	inset: 0;
	z-index: 40;
	background: rgba(10, 9, 14, 0.85);
	display: grid;
	place-items: center;
	padding: 16px;
}
#confirmBox {
	width: min(420px, 100%);
	padding: 18px;
	background: var(--surface);
	border: 1px solid var(--line);
	border-radius: 12px;
	box-shadow: 0 12px 34px rgba(0, 0, 0, 0.55);
}
#confirmBox h2 {
	margin: 0 0 8px;
	font-family: var(--font-display);
	font-size: 18px;
	font-weight: 400;
}
#confirmBox p {
	margin: 0 0 16px;
	font-size: 13.5px;
	line-height: 1.5;
	color: var(--muted);
}
/* Cancel is first in the DOM, so it is where focus lands and where Tab starts,
   and it reads left to right in the order every other dialog uses. */
#confirmRow { display: flex; justify-content: flex-end; gap: 8px; }

#picker {
	position: fixed;
	inset: 0;
	z-index: 30;
	background: rgba(10, 9, 14, 0.85);
	display: none;
	padding: 12px;
}
#picker.on { display: grid; place-items: center; }
#pickerBox {
	width: min(560px, 100%);
	max-height: min(78dvh, 700px);
	display: grid;
	grid-template-rows: auto auto 1fr;
	background: var(--surface);
	border: 1px solid var(--line);
	border-radius: 12px;
	overflow: hidden;
}
#pickerHead { display: flex; align-items: center; gap: 10px; padding: 12px 14px; border-bottom: 1px solid var(--line); }
#pickerHead h3 { margin: 0; font-family: var(--font-display); font-size: 17px; font-weight: 400; flex: 1; }
#pickerSearch { margin: 10px 14px; padding: 9px 11px; background: var(--ground); border: 1px solid var(--line); border-radius: 7px; font-size: 15px; }
#pickerSearch:focus { border-color: var(--accent-dim); }
#pickerSearch:focus-visible { outline: 2px solid var(--accent); outline-offset: -1px; }
#pickerList { overflow-y: auto; padding: 0 8px 12px; }
.pick {
	display: flex; gap: 10px; align-items: center; width: 100%;
	padding: 8px; background: none; border: none; border-radius: 8px;
	text-align: left; cursor: pointer; min-height: 52px;
}
.pick:hover { background: var(--surface-2); }
.pick .ico { width: 34px; height: 34px; flex: none; border-radius: 5px; background: var(--ground); }
.pick .pb { flex: 1; min-width: 0; }
.pick .nm { display: block; font-size: 14px; }
.pick .meta { display: block; font-size: 11px; color: var(--muted); }
/* What the item actually rolls, so a choice does not require equipping it. */
.pick .afx {
	display: block;
	font-family: var(--font-data);
	font-size: 10.5px;
	color: var(--path);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	margin-top: 1px;
}
/* Developer content: kept and reachable, but never mistaken for a real item. */
.devtag {
	margin-left: 7px;
	padding: 1px 5px;
	border-radius: 4px;
	border: 1px solid var(--danger);
	color: var(--danger);
	font-family: var(--font-data);
	font-size: 9.5px;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	vertical-align: 1px;
}

.pick .sock {
	margin-left: 7px;
	color: var(--find);
	font-family: var(--font-data);
	font-size: 10.5px;
}
.pick .hit {
	margin-left: 7px;
	font-family: var(--font-data);
	font-size: 11px;
	color: var(--accent);
}
.pick { align-items: flex-start; }
.pick .ico { margin-top: 2px; }

/* ---------- detail / stats ---------- */

#detail h3 { margin: 0 0 2px; font-family: var(--font-display); font-size: 17px; font-weight: 400; text-wrap: balance; }
#detail .meta { font-family: var(--font-data); font-size: 11px; color: var(--muted); margin-bottom: 10px; }
#detail p.desc { font-size: 14px; margin: 0 0 12px; }

.build {
	font-family: var(--font-data);
	font-size: 10px;
	letter-spacing: 0.1em;
	color: var(--muted);
	opacity: 0.8;
}

.note {
	margin-top: 22px; padding-top: 12px;
	border-top: 1px solid var(--line);
	font-size: 11.5px; line-height: 1.45; color: var(--muted);
}

/* ---------- wider screens ---------- */

@media (min-width: 720px) {
	.skills { grid-template-columns: repeat(2, minmax(0, 1fr)); }
	.slots { grid-template-columns: repeat(3, minmax(0, 1fr)); }
	.pad { padding: 20px 20px 32px; }
}

/* Past this the tabs give way to a permanent stats rail, and the tree and the
   numbers can be read at the same time. */


/* ---------- always-visible build summary ---------- */

#summary {
	background: var(--surface);
	border-top: 1px solid var(--line);
	display: flex;
	flex-direction: column;
	min-height: 0;
}

#summaryToggle {
	display: flex;
	align-items: center;
	gap: 10px;
	width: 100%;
	min-height: var(--strip);
	padding: 0 14px;
	background: none;
	border: none;
	cursor: pointer;
	text-align: left;
}
#summaryToggle .chev {
	width: 18px; height: 18px;
	flex: none;
	color: var(--muted);
	transform: rotate(180deg);
	transition: transform 140ms ease;
}
#summaryToggle[aria-expanded="true"] .chev { transform: rotate(0deg); }

/* The strip carries the numbers you glance at without opening anything. */
#summaryStrip {
	flex: 1;
	min-width: 0;
	display: flex;
	gap: 14px;
	overflow-x: auto;
	scrollbar-width: none;
	font-family: var(--font-data);
	font-variant-numeric: tabular-nums;
	font-size: 12.5px;
	white-space: nowrap;
}
#summaryStrip::-webkit-scrollbar { display: none; }
#summaryStrip .k { color: var(--muted); }
#summaryStrip .v { color: var(--accent); }
#summaryStrip .none { color: var(--muted); font-family: var(--font-ui); font-style: italic; }
/* A stat the game data never named. Equipping one helmet used to fill this strip
   with "+587 +586 +585 +584" and no keys at all, because the key rendered empty;
   the id now stands in and is marked so it cannot be mistaken for a real name.
   Dotted rather than dimmed: this panel is always on screen and something the
   reader has to squint at is not a warning. See fallbackLabel in main.js. */
#summaryStrip .k.unnamed {
	border-bottom: 1px dotted var(--danger-text);
	color: var(--danger-text);
}

/* max-height alone only clips the panel: its contents keep their boxes, so a
   collapsed summary holds every one of its buttons in the tab order, invisible.
   That is fixed with `inert` in setSummaryOpen rather than with `visibility`
   here, because visibility also stops the panel being rendered at all and the
   totals are read out of it by more than one caller. */
#summaryBody {
	max-height: 0;
	overflow-y: auto;
	overscroll-behavior: contain;
	padding: 0 14px;
	transition: max-height 160ms ease, padding 160ms ease;
}
#summary.open #summaryBody {
	max-height: min(58dvh, 460px);
	padding: 2px 14px 18px;
}

/* Each stat shows where it came from, since the whole point is seeing how the
   four facets add up rather than just the total. */
.stat {
	padding: 6px 0;
	border-bottom: 1px solid rgba(51, 46, 63, 0.5);
}
.stat:last-child { border-bottom: none; }
.stat .head {
	display: flex;
	justify-content: space-between;
	gap: 12px;
	align-items: baseline;
	width: 100%;
	margin: 0;
	padding: 0;
	background: none;
	border: none;
	color: inherit;
	font: inherit;
	text-align: left;
}
/* 24px is the WCAG 2.2 target minimum, and the row measured 23. */
button.head { cursor: pointer; min-height: 24px; }
button.head:focus-visible { outline: 2px solid var(--path); outline-offset: 2px; }
.stat .head .nm { font-size: 13.5px; }
.stat .head .vals { display: flex; gap: 8px; white-space: nowrap; }
.stat b {
	font-family: var(--font-data);
	font-variant-numeric: tabular-nums;
	font-weight: 500;
	color: var(--accent);
}
.stat b.pct { color: var(--path); }
.stat b.tot { color: var(--total); }
.stat b.res { font-size: 15px; }
.stat b.res .cap {
	font-family: var(--font-ui);
	font-size: 9.5px;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--danger);
	margin-left: 5px;
}
/* The breakdown's half of the unnamed-stat marker. --danger-text rather than
   --danger, which is a fill colour and sits at 4.21:1 on this panel. */
.stat .unk {
	font-size: 9.5px;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--danger-text);
	margin-left: 6px;
}

/* ---------- grants ----------

   An item stat that holds a talent id is not a stat, and the summary used to
   present four of them as "Granted Aura 5 +587". Nothing below invents a new
   visual language for the corrected version: a grant is a .stat row with the
   same rule, spacing and facet pills as the numbers above it. The only thing it
   does differently is refuse to look like a quantity, which is the entire point.
   No signed value, no tabular figures, no accent gold. */
/* The grants sit above the numbers under their own heading. The rule separates
   two things that must not be read as one list, which is what went wrong: four
   talent ids were sorted in among the stats by magnitude and looked exactly like
   them. */
#summaryBody .label.sub {
	margin-top: 4px;
	padding-top: 10px;
	border-top: 1px solid var(--line);
}
.stat.grant + .stat:not(.grant) {
	margin-top: 10px;
	padding-top: 12px;
	border-top: 1px solid var(--line);
}
/* The name keeps the line and the level wraps under it, rather than the other
   way round. Without this "PROCS  level 12-18 · 7% on attack" took the first line
   and pushed "Live by the Sword" onto the second, which reads as though the row
   is about the numbers. It is about the skill. */
.stat.grant .head { align-items: baseline; flex-wrap: wrap; }
.stat.grant .nm { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 6px; flex: 1 1 auto; }
.stat.grant .vals { flex: 0 1 auto; }
/* "Grants aura" ahead of the talent's name, in the same key-and-value weighting
   the strip uses, so the row reads as a sentence at a glance. */
.stat.grant .gk {
	font-size: 9.5px;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--muted);
}
/* The level and chance. --muted rather than --accent because --accent is what
   every real number on this panel is drawn in, and a level is a property of the
   grant rather than a total the build has earned. */
.stat.grant b.gv {
	font-weight: 400;
	font-size: 12px;
	color: var(--muted);
}
/* A count of sources, not a value, so the strip's grant chip stays the one thing
   in that row without a plus sign in front of it. */
#summaryStrip .v.gr { color: var(--muted); }

/* The same block in the hover tooltip. */
#tip .row2.grantrow .src b { font-family: var(--font-ui); color: var(--ink); }
#tip .row2.grantrow .src i {
	font-style: normal;
	color: var(--danger-text);
	font-size: 10px;
	border-bottom: 1px dotted var(--danger-text);
}

/* The buckets behind the number, shown only when more than one contributed. */
.workings {
	font-family: var(--font-data);
	font-size: 10.5px;
	color: var(--muted);
	margin-top: 2px;
}

.facets {
	display: flex;
	flex-wrap: wrap;
	gap: 4px 6px;
	margin-top: 3px;
}
.facet {
	font-family: var(--font-data);
	font-size: 10.5px;
	line-height: 1.5;
	padding: 1px 7px;
	border-radius: 999px;
	border: 1px solid transparent;
	/* Without this the pill wraps its own text and renders as a circle. */
	white-space: nowrap;
}
.facet.gear  { color: #d3a06a; border-color: rgba(211,160,106,0.35); }
.facet.attr  { color: #8fbf7a; border-color: rgba(143,191,122,0.35); }
.facet.skill { color: #6ba3d6; border-color: rgba(107,163,214,0.35); }
.facet.tree  { color: #b98ad0; border-color: rgba(185,138,208,0.35); }
.facet.relic { color: #d67a6b; border-color: rgba(214,122,107,0.35); }
.facet.charm { color: #6fc2b4; border-color: rgba(111,194,180,0.35); }
.facet.socket{ color: #9aa7c4; border-color: rgba(154,167,196,0.35); }
.facet.ether { color: #7fd1a0; border-color: rgba(127,209,160,0.35); }

/* ---------- recommendations ---------- */

/* A stat row is a control: pressing it ranks the tree routes that raise it.

   This is the product's headline feature and its entire entry point was the word
   "how?" at 10px and 45% opacity, measuring 31 by 14 pixels. Three things were
   wrong with that and all three are fixed here: it did not look like a control,
   it did not say what it did, and it was not legible. It is now a pill with a
   border, at full opacity, carrying a verb. The row is only drawn as a button at
   all where the recommender has something to say, so the pill is always an offer
   that can be honoured. */
.stat button.head:hover .ask { background: rgba(107, 163, 214, 0.16); }
.stat .ask {
	display: inline-block;
	margin-left: 8px;
	padding: 1px 8px;
	font-size: 10.5px;
	line-height: 1.5;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	white-space: nowrap;
	color: var(--path);
	border: 1px solid rgba(107, 163, 214, 0.55);
	border-radius: 999px;
	transition: background 120ms ease;
}
.stat.open .ask { background: rgba(107, 163, 214, 0.16); }
.stat.open { background: rgba(107, 163, 214, 0.05); border-radius: 8px; padding: 8px; margin: 0 -8px; }

.advice { display: flex; flex-direction: column; gap: 6px; margin-top: 9px; }

.offer {
	display: flex;
	align-items: center;
	gap: 10px;
	width: 100%;
	padding: 7px 9px;
	background: var(--ground);
	border: 1px solid var(--line);
	border-radius: 7px;
	text-align: left;
	cursor: pointer;
	min-height: 46px;
}
.offer:hover { border-color: var(--path); }
.offer:focus-visible { outline: 2px solid var(--path); outline-offset: 2px; }

.offer .og {
	flex: none;
	min-width: 58px;
	font-family: var(--font-data);
	font-variant-numeric: tabular-nums;
	font-size: 14px;
	color: var(--accent);
}
.offer .ob { flex: 1; min-width: 0; }
.offer .on {
	display: block;
	font-size: 12.5px;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.offer .oc {
	display: block;
	font-family: var(--font-data);
	font-size: 10.5px;
	color: var(--muted);
}
.offer .ot {
	flex: none;
	font-size: 10.5px;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--path);
}

/* ---------- attributes ---------- */

.attrs { display: grid; gap: 8px; grid-template-columns: repeat(2, minmax(0, 1fr)); }
.attr {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 7px 9px;
	background: var(--surface);
	border: 1px solid var(--line);
	border-radius: 8px;
}
.attr .nm { flex: 1; min-width: 0; font-size: 13px; text-transform: capitalize; }
/* The number is the control. Capping one attribute was 401 presses of a 32px
   button and a whole character was 2,400, so this is editable in place and the
   steppers became the fine adjustment rather than the only adjustment. It is
   dressed as a field, because a number that can be typed into and does not look
   like it can is the same as one that cannot. */
.attr .v {
	font-family: var(--font-data);
	font-variant-numeric: tabular-nums;
	font-size: 14px;
	color: var(--accent);
	/* A fixed width, not a minimum. 400 is the widest value the budget allows and
	   46px holds it with room, but a min-width still lets a mid-edit paste widen
	   the field and shove the two steppers along the row. The steppers must not
	   move for any reason, so the field cannot grow for any reason either; the
	   name beside it is the flexible half. tabular-nums keeps the digits from
	   shifting inside the fixed box as they change. */
	flex: none;
	width: 46px;
	min-height: 32px;
	padding: 4px 6px;
	background: var(--ground);
	border: 1px solid var(--line);
	border-radius: 6px;
	text-align: right;
	cursor: text;
	/* A contenteditable wraps and grows; this one holds one short number. */
	white-space: nowrap;
	overflow: hidden;
}
.attr .v:hover { border-color: var(--accent-dim); }
.attr .v:focus { border-color: var(--accent-dim); outline: none; }
.attr .v:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.attr button {
	width: 32px; height: 32px;
	flex: none;
	border-radius: 6px;
	background: var(--surface-2);
	border: 1px solid var(--line);
	font-size: 16px;
	line-height: 1;
	cursor: pointer;
}
.attr button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The Shift step existed and nothing said so, which is the same as not having
   it. On its own line so it can wrap without pushing the budget note around. */
.label .sub2.tip { display: block; font-size: 11.5px; }
/* Overspent, which only a level decrease can cause. --danger-text rather than
   --danger: the fill colour is 4.21:1 on this surface and this is body text at
   the same size as the readout it replaces. Deliberately loud, because the
   allocation underneath it is kept rather than clamped and the visitor is the
   only one who can decide which points to give back. */
#attrPool.over { color: var(--danger-text); }
@media (min-width: 720px) {
	.attrs { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* Past this the tabs move under the header and the stats become a permanent
   rail, so the board and the numbers are readable at once. */
@media (min-width: 1000px) {
	/*
	 * The three bands, and why they are shorter here than on a phone.
	 *
	 * This is the only layout where all three are stacked: masthead, then the
	 * site strip, then the tab strip, one on top of another before the planner
	 * starts. At the phone sizes each row was measured for they never appear
	 * together, because the tabs are at the foot of the screen. Together they
	 * came to 161px, and 161px of chrome above a tool is what the site owner
	 * called a grey blob.
	 *
	 * 138px now, and the height comes off the two rows that can spare it rather
	 * than off the masthead, which has just been given a mark and a bigger
	 * wordmark to hold. The 44x44 touch figure that sized both of them is
	 * advice for a finger; the rule that binds is 24x24, and a 40px strip whose
	 * links stretch the full height of it clears that with sixteen to spare.
	 */
	:root {
		/* --nav is NOT set here. It was, at 40, and a second declaration of it at
		   42 sat in the design-g block at the foot of this file: the later one
		   won and the earlier one read like the answer, which is how it came to
		   be edited twice with no effect. One token, one place. */
		/* 38, the design's figure, and it is a mouse target rather than a finger
		   one. The 44px minimum this row used to hold is advice for touch, and
		   this strip is only ever this height on a pointer device: on a phone it
		   is a bottom bar at the full 58 with the 44px buttons intact. The rule
		   that binds a pointer target is 24x24, which 38 clears comfortably. */
		--tabs: 38px;
		/* The design's page padding and gutter. 28 either side of the 1600px
		   container and 28 between each pair of columns: 160 + 28 + 984 + 28 +
		   344 comes to the 1544 a padded 1600 leaves, which is where the 984
		   the cards were drawn at comes from. */
		--pad: 28px;
		--gut: 28px;
		/* The tab strip is under the header here and the summary is a rail down
		   the right, so nothing is stacked at the foot of the window and the
		   consent banner has nothing to clear but the edge of the glass. */
		--consent-lift: 6px;
	}
	/*
	 * The shell: pillar, workspace, rail, inside a centred container.
	 *
	 * It was two columns edge to edge, so at 1920 the workspace ran the full
	 * width of the glass and every card in it stretched with the window: gear
	 * cards measured 441px against the design's 241, and relic cards 667
	 * against 366. A tool with no measure is a different layout at every
	 * screen size, which is why the build and the design diverged more the
	 * wider the window got.
	 *
	 * 1600 and not 100%: the design's own container. The bars stay full bleed
	 * (see the clip-path below) so the chrome still spans the glass while the
	 * content inside it holds a measure.
	 */
	/*
	 * The shell, written to the design's own three numbers.
	 *
	 * The design declares one container and one gutter and every row on the page
	 * agrees with them: a 1600px measure, 28px of page padding, 28px between
	 * columns, a 160px pillar and a 344px Ledger. What is left in the middle is
	 * 984px, and 984 is the width every card inside the workspace was drawn at.
	 *
	 * The build got to 984 a different way: it left the column at 1076 and
	 * padded the pane 46px each side to fake the measure. The content came out
	 * the right width and nothing else did. The tab strip and the compartment
	 * head were 1076 wide because they are not the pane, the Ledger started 39px
	 * late because the gutters it was supposed to sit after did not exist, and
	 * the pillar sat 9px off for the same reason. One track sized correctly and
	 * padding standing in for the rest is not the same layout as the design; it
	 * is a layout that agrees with the design in one place.
	 *
	 * So the grid carries the measure now. The gutters are real, every row
	 * starts at the same 181px, and the pane's padding is gone.
	 */
	#app {
		grid-template-rows: var(--bar) var(--nav) var(--tabs) 1fr;
		grid-template-columns: minmax(0, 1fr) var(--ledger);
		column-gap: var(--gut);
		max-width: var(--max);
		margin: 0 auto;
		padding: 0 var(--pad);
	}
	/*
	 * FULL BLEED for the chrome, and why it is a fixed pseudo-element.
	 *
	 * The shell is a centred 1600px container, so the masthead and the site
	 * strip inside it stop at its edges and the window shows the page ground
	 * either side of them: a header floating in the middle of the glass with a
	 * gap down both sides. The chrome is site furniture and has to run the whole
	 * width, however narrow the content inside it is measured.
	 *
	 * The usual trick for that is a 100vmax box-shadow with a clip-path holding
	 * it to the row, and it was here and it did nothing, because #app is
	 * `overflow: hidden` and an ancestor's overflow clips a descendant's shadow.
	 * That is also why it looked correct in the stylesheet and wrong on screen.
	 *
	 * A fixed element is not clipped by an ancestor's overflow (only by a
	 * transformed one, and nothing here is transformed), so each row paints its
	 * own colour as a fixed stripe across the window, behind itself. z-index -1
	 * puts it under the row's own background and content while still leaving it
	 * above the page canvas, so the stripe is only ever visible in the margins
	 * the container does not reach.
	 */
	#bar::before, #sitenav::before {
		content: "";
		position: fixed;
		left: 0;
		right: 0;
		z-index: -1;
		pointer-events: none;
	}
	#bar::before {
		top: 0;
		height: var(--bar);
		background: var(--surface);
	}
	/* The strip's ground is the page's ground, so the stripe carries only the
	   hairline that ends the header. Without it the rule stopped dead at the
	   container edge and started again nowhere. */
	#sitenav::before {
		top: var(--bar);
		height: var(--nav);
		background: var(--ground);
		border-bottom: 1px solid var(--line);
	}
	/* No padding of its own. The shell's 28px is the page's left edge and every
	   row on it starts there: the mark, the first navigation link, the tab
	   strip, the compartment head and the first gear card all line up on one
	   vertical. Two rows of a header that start at different places is the kind
	   of thing nobody names and everybody sees, and the way to guarantee they
	   cannot is to give the padding to the container rather than to each row. */
	#bar   { grid-row: 1; grid-column: 1 / -1; padding: 0; }
	/* Full width, above the rail as well as the panes. It is site chrome, not
	   part of the planner, so it runs the whole way across exactly as #bar does
	   and the rail starts underneath it. Its own padding goes for the same
	   reason #bar's did: the container owns the page's left edge.

	   The links lose their own padding with it and are spaced by a 24px gap
	   instead, which is the design's arrangement and is what puts the first
	   link's text on the same vertical as the wordmark above it. The padding
	   was there to give each link a 44px-wide finger target; this row is only
	   ever laid out this way on a pointer device, and the phone layout below
	   1000px keeps the padded targets untouched. */
	#sitenav { grid-row: 2; grid-column: 1 / -1; padding: 0; gap: 24px; }
	#sitenav a { padding: 0; }
	#sitenav a:hover { background: none; }
	/* The stats tab is redundant once the rail is permanent.

	   Ground rather than surface, and this is the other half of un-blobbing the
	   header. On a phone this strip is a bottom bar and has to lift off the pane
	   it sits over, so it keeps the raised grey. Here it sits directly under two
	   grey rows, and a third one made all three a single field of colour 161px
	   deep. On the pane's own ground it belongs to the panes instead, which is
	   what it is: the masthead and the site strip say where you are on the site,
	   and this says which part of a build you are editing. The rule underneath
	   and the accent bar over the live tab are what draw it now. */
	#tabs  {
		grid-row: 3; grid-column: 1;
		background: var(--ground);
		border-top: none; border-bottom: 1px solid var(--line);
	}
	/* The live tab is marked on the bottom edge here, not the top.
	   On a phone this strip is the last thing on the screen and its outer edge
	   is the top one, which is where the mark belongs. Here the strip is
	   directly beneath the site navigation, whose own current-section underline
	   is 2px of the same gold: the two landed five pixels apart, one under
	   "Planner" and one over "Gear", and read as one line that had gone wrong.
	   Moved down, it sits against the pane it opens, which is what it means. */
	#tabs button { border-top: none; border-bottom: 2px solid transparent; }
	#tabs button.on { border-bottom-color: var(--accent); }
	#panes { grid-row: 4; grid-column: 1; }
	/* With room, the summary is a rail: always open, no strip, no toggle. */
	#summary {
		grid-row: 3 / span 3;
		grid-column: 2;
		border-left: 1px solid var(--line);
		border-top: none;
		overflow-y: auto;
	}
	#summaryToggle { display: none; }
	/* A phone anchor on a desktop is wasted space and looks like a mistake. */
	#adAnchor { display: none; }
	/* Disabled: the rail holds the build summary, and that panel is the thing a
	   player reads constantly. Reserved markup is still present, so turning it
	   back on is one selector. The empty rule still applies over the top of
	   this one, so switching data-ads to "on" reveals the slot only once there
	   is something in it. */
	:root[data-ads="on"] #adRail[data-filled] {
		display: grid;
		flex: none;
		width: 300px; height: 250px;
		margin: 18px auto 24px;
	}
	#summaryBody { max-height: none !important; padding: 18px 18px 30px; }
	.slots { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/*
 * THE LEFT PILLAR, and the third column.
 *
 * Gated at 1241 because that is where the design's own ladder drops it, and the
 * arithmetic behind that number is the reason to keep it: below 1241 the
 * container is narrower than 160 + 28 + 984 + 28 + 344, so something has to
 * give, and the column that gives is the one holding no build data.
 *
 * The track is 160 and not 184. It used to be the creative plus a pair of
 * gutters baked into the track, which meant the gutter was declared in two
 * places; the grid owns its gutters now, so the track is exactly the creative.
 *
 * It is also no longer conditional on the slot being filled. That condition was
 * there to stop an unsold slot costing the planner 184px of nothing, which was
 * the right instinct against a track that drew an invisible hole. The design
 * answers it differently and better: the slot draws a labelled dashed
 * rectangle, so the reserved space says what it is reserved for instead of
 * looking like a rendering fault, and every column either side of it sits where
 * the design puts it whether or not there is an advertisement in the building.
 */
@media (min-width: 1241px) {
	#app { grid-template-columns: var(--pillar) minmax(0, 1fr) var(--ledger); }
	#app > #bar      { grid-column: 1 / -1; }
	#app > #sitenav  { grid-column: 1 / -1; }
	#app > #adPillar {
		display: block;
		/* From the compartment head to the foot of the shell. The rows here are
		   bar, strip, head, tabs, panes, so "span 2" stopped it at the tab
		   strip. */
		grid-row: 3 / -1;
		grid-column: 1;
		align-self: start;
		/* Sticky, so a 600px creature beside a scrolling workspace stays with
		   the reader rather than leaving at the first screenful. The offset is
		   the design's own top padding for the shell. */
		position: sticky;
		top: 32px;
		width: var(--pillar);
		margin: 32px 0 0;
	}
	#app > #workbench { grid-column: 2; }
	#app > #tabs     { grid-column: 2; }
	#app > #panes    { grid-column: 2; }
	#app > #summary  { grid-column: 3; }
}

/* The reserved rectangle. Dashed rather than solid and labelled with its own
   dimensions, which is the difference between a slot and a mistake. It is
   replaced outright the moment a creative lands, so nothing a network draws
   ever inherits a dashed border. */
.adslot {
	display: grid;
	place-items: center;
	width: var(--pillar);
	height: 600px;
	border: 1px dashed var(--line-2, var(--line));
	border-radius: 6px;
	color: var(--muted);
	font-family: var(--font-display);
	font-size: 11px;
	letter-spacing: .14em;
	text-transform: uppercase;
}
#adPillar[data-filled] .adslot { display: none; }

/*
 * THE RESPONSIVE LADDER, the design's own.
 *
 * Three steps down from the full three-column shell, and each one gives up the
 * column that costs the reader least. Written as max-width so each step reads
 * as "what goes at this width", and placed after the blocks above so that a
 * narrower rule wins on the tie.
 */
/* The Ledger gives up 32px before anything is dropped: at 1440 the workspace is
   already down to 876 and the panel is the one column with slack in it. */
@media (max-width: 1420px) { :root { --ledger: 312px; } }
/* Below 980 the rail stops being a column at all. The 1000px block above is
   what turns it into one, so there is nothing to undo here: the phone layout
   underneath it is already the correct answer. */
@media (max-width: 760px) {
	:root { --pad: 16px; --gut: 16px; }
}

/* Stated once, for both properties. The rule used to appear twice and name
   only `transition`, so any animation added later would have run regardless of
   what the visitor had asked their system for. */
@media (prefers-reduced-motion: reduce) {
	*, *::before, *::after {
		transition: none !important;
		animation: none !important;
	}
}


/* ---------- segmented control ---------- */

/* Left behind by the farming and runeword panes, which is where it was written
   and where every other rule in that block died with them. The item editor's
   tier picker uses it too, so it outlived them. */
.seg {
	background: var(--surface-2);
	border: 1px solid var(--line);
	border-radius: 6px;
	padding: 8px 12px;
	font-size: 13px;
	min-height: 38px;
	cursor: pointer;
	white-space: nowrap;
}
.seg.on { border-color: var(--accent); color: var(--accent); }


/* ---------- item editor ---------- */

/* The lower area used to repeat every equipped affix, which every tooltip
   already says. It is worth more as the place you describe an item you own. */
.editor {
	margin-top: 24px;
	padding: 14px 16px 18px;
	background: var(--surface);
	border: 1px solid var(--accent-dim);
	border-radius: 10px;
}
.edhead { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.edhead h3 { margin: 0; font-family: var(--font-display); font-size: 18px; font-weight: 400; }
.edrow {
	display: flex;
	align-items: center;
	gap: 10px;
	flex-wrap: wrap;
	margin: 12px 0;
}
.edrow .label { margin: 0; min-width: 74px; }
.edtiers { display: flex; gap: 4px; }
.edtiers .seg { padding: 6px 10px; min-width: 34px; font-family: var(--font-data); }
/* A tier the client never defines this base at. Shown, so the band is legible,
   but plainly not one of its own. */
/* 0.4 measured 3.43:1. The dashed border already says "not at this tier". */
.edtiers .seg.ghost { opacity: 0.6; border-style: dashed; }
.edrow select {
	background: var(--ground);
	border: 1px solid var(--line);
	border-radius: 6px;
	padding: 7px 9px;
	min-height: 36px;
}

.edaffix {
	margin: 8px 0;
	padding: 8px 10px;
	background: var(--ground);
	border: 1px solid var(--line);
	border-radius: 8px;
}
/* The roll: what it is, where in its range you put it, and what the range was.
   Showing the range matters, because "47" means nothing without "12 to 50". */
.edroll {
	display: grid;
	grid-template-columns: minmax(0, 1fr) 130px 44px auto;
	align-items: center;
	gap: 8px;
	padding: 3px 0;
	font-size: 13px;
}
.edroll b {
	font-family: var(--font-data);
	font-variant-numeric: tabular-nums;
	text-align: right;
	color: var(--accent);
}
.edroll input[type="range"] { width: 100%; accent-color: var(--accent); }

.tierpill {
	margin-left: 7px;
	padding: 1px 6px;
	border-radius: 4px;
	border: 1px solid var(--accent-dim);
	color: var(--accent);
	font-family: var(--font-data);
	font-size: 10px;
	letter-spacing: 0.06em;
}

/* Crafting is offered where the choosing happens: the first row of the picker.
   It was a pencil on every slot, which looked like clutter and was not
   findable. */
.pick.craftrow { border: 1px dashed var(--accent-dim); }
.pick.craftrow .nm { color: var(--accent); }

@media (max-width: 560px) {
	.edroll { grid-template-columns: minmax(0, 1fr) 44px; }
	.edroll input[type="range"] { grid-column: 1 / -1; }
	.edroll .src:last-child { display: none; }
}


/* The craft row's anvil, drawn inline rather than pulled from an atlas. */
.ico.anvilbox { border: 1px dashed var(--accent-dim); background: none; }
.anvil { width: 20px; height: 20px; color: var(--accent); }
/* The tier and window an affix actually rolled at, on a crafted item. */
#tip .row2 .rolled {
	margin-left: 6px;
	color: var(--accent);
	font-style: normal;
	font-family: var(--font-data);
	font-size: 10px;
}



/* ==========================================================================
   DIRECTION G additions
   Everything above is the existing stylesheet with a new palette under it.
   What follows is new chrome the direction asked for and the file did not have.
   ========================================================================== */

/* The season and client stamp, right-aligned in the navigation strip.
   Hero Siege restarted its season numbering after the 2.0 relaunch, so the
   season alone is ambiguous: the client build date is what pins it. */
#sitenav .season {
	margin-left: auto;
	padding-right: 4px;
	font-family: var(--font-data);
	font-size: 12px;
	color: var(--muted);
	white-space: nowrap;
	align-self: center;
}
#sitenav .season b { color: var(--ink); font-weight: 400; }
/* 900, not 760. The stamp is 197px wide and it is pushed to the right of the
   strip, so between 761 and 900 it hung past the edge of the glass: on the
   privacy page, whose strip does not scroll, that became eleven pixels of
   horizontal scrollbar on the whole document. It is a client build date, not
   navigation, and it is the first thing in the row that can go. */
@media (max-width: 900px) { #sitenav .season { display: none; } }

/* Relics: two across, and the fifth centred rather than stretched.
   Five slots means the last row holds one, and a card spanning both columns
   reads as a different kind of object from the four above it. */
@container pane (min-width: 700px) {
	.slots.aux.relics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
	.slots.aux.relics > :last-child:nth-child(odd) {
		grid-column: 1 / -1;
		justify-self: center;
		width: calc(50% - 3px);
	}
}

/* ---------- masteries ----------
   3,108 sub-talent nodes across the game, and until now the one part of a
   build the planner could show but not plan. A row is a name, what it does,
   and a stepper; a locked one says what opens it instead of the stepper. */
.mrow {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 6px 0;
	border-top: 1px solid var(--line);
	font-size: 13.5px;
}
.mrow:first-of-type { border-top: 0; }
.mrow .mn { flex: 1; min-width: 0; line-height: 1.35; }
.mrow .mv {
	font-family: var(--font-data);
	font-size: 12.5px;
	color: var(--muted);
	min-width: 44px;
	text-align: center;
	font-variant-numeric: tabular-nums;
}
.mrow.al .mv { color: var(--class); }
.mrow button {
	width: 26px; height: 26px; flex: 0 0 26px;
	border: 1px solid var(--line);
	border-radius: 4px;
	background: none;
	display: grid; place-items: center;
	color: var(--muted);
	line-height: 1;
}
.mrow button:hover:not(:disabled) { border-color: var(--class); color: var(--class); }
.mrow button:disabled { opacity: .3; cursor: default; }
/* Locked: dimmed and struck through, never hidden. "What do I need for this
   build" is unanswerable if the tree only draws what you already have. */
.mrow.locked { opacity: .55; }
.mrow.locked .mn { text-decoration: line-through; text-decoration-color: var(--line); }
.mrow.locked .src { text-decoration: none; display: inline-block; }

/* ==========================================================================
   THE WORKBENCH and THE LEDGER
   Two compartment heads, one on each side of the app. The rule across the gap
   is the whole device: it turns a heading into a labelled container, which is
   what tells you the tabs beneath belong to one character rather than being
   four unrelated places.
   ========================================================================== */
#workbench { display: none; }
#ledgerHead { display: none; }

.lozenge {
	width: 5px; height: 5px; flex: 0 0 5px;
	transform: rotate(45deg);
	background: var(--class);
	transition: background .25s;
}

@media (min-width: 1000px) {
	#workbench {
		display: flex;
		align-items: center;
		gap: 14px;
		grid-row: 3;
		grid-column: 1;
		height: 26px;
		/* Flush with its column, like everything else in it. The 46 here was the
		   third copy of the pane-padding that stood in for the shell's missing
		   gutters, and with the gutters real it put "The Workbench" 46px right
		   of the tab under it and of the first gear card under that. */
		padding: 0;
		margin-top: 14px;
	}
	/* The tab strip moves down a row to sit under the compartment head. */
	#app { grid-template-rows: var(--bar) var(--nav) 40px var(--tabs) 1fr; }
	#tabs { grid-row: 4; }
	#panes { grid-row: 5; }
	#summary { grid-row: 3 / -1; }

	#workbench h2, #ledgerHead h2 {
		margin: 0;
		font-family: var(--font-display);
		font-size: 15px;
		font-weight: 600;
		letter-spacing: .13em;
		text-transform: uppercase;
		color: var(--ink);
		white-space: nowrap;
	}
	#workbench .rule, #ledgerHead .rule { flex: 1; height: 1px; background: var(--line); }
	#workbench .kicker, #ledgerHead .kicker {
		font-family: var(--font-display);
		font-size: 11px;
		font-weight: 600;
		letter-spacing: .14em;
		text-transform: uppercase;
		color: var(--muted);
		white-space: nowrap;
	}
	#ledgerHead {
		display: flex;
		align-items: center;
		gap: 14px;
		height: 26px;
		margin: 14px 0 10px;
		padding: 0 14px 0 0;
	}
}

/* Tab counts. `24 / 24` beside the label, in the data face, so the strip says
   how much of each part of the build is spent without being opened. */
#tabs .tcount {
	font-family: var(--font-data);
	font-size: 11px;
	line-height: 1;
	color: var(--muted);
	font-variant-numeric: tabular-nums;
	margin-left: 6px;
}
#tabs button[aria-selected='true'] .tcount { color: var(--accent); }
@media (max-width: 560px) { #tabs .tcount { display: none; } }

/* ==========================================================================
   CARD PROPORTIONS AND RARITY
   ========================================================================== */

/* The rarity keyline. Colour on the edge and on the name, never as a fill:
   a coloured tile behind an item name is a slab, and removing slabs is most
   of what this direction is. */
/* Aux cards still carry their own frame, so the edge stays on them; the doll's
   cards are framed by their wrapper and it carries the edge instead. */
.slots.aux .slot[data-r], .slot.aux[data-r] { border-left-color: var(--rr, var(--line)); }
.slot[data-r] .it { color: var(--rr, var(--ink)); }
[data-r='unique']  { --rr: var(--r-unique); }
[data-r='normal']  { --rr: var(--r-normal); }
[data-r='magic']   { --rr: var(--r-magic); }
[data-r='rare']    { --rr: var(--r-rare); }
[data-r='mythic']  { --rr: var(--r-mythic); }
[data-r='satanic'] { --rr: var(--r-satanic); }
[data-r='angelic'] { --rr: var(--r-angelic); }
[data-r='heroic']  { --rr: var(--r-heroic); }
[data-r='unholy']  { --rr: var(--r-unholy); }
[data-r='rune']    { --rr: var(--r-rune); }

/* The third line of a card: what the item is, under what it is called. */
.slot .sub3 {
	font-size: 12px;
	color: var(--muted);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	display: block;
}
.slot:not(.filled) .sub3 { color: var(--line-2, var(--muted)); opacity: .65; }

@media (min-width: 1000px) {
	/*
	 * The workspace, and why it no longer pads itself.
	 *
	 * The pane used to carry 46px either side, which was a measured number
	 * standing in for a missing one: the column was 1076 because the shell had
	 * no gutters, and 1076 less 92 is the 984 the cards were drawn at. The
	 * content came out right and nothing around it did. The shell owns the
	 * measure now, so the column is 984 on its own and the pane is flush with
	 * it, which is how the tab strip and the compartment head come to start on
	 * the same vertical as the first gear card.
	 *
	 * 984 splits into the design's two parts: a 740px doll and a 212px charm
	 * board, 32 apart.
	 */
	/* No side padding on the pane. The shell's 28px is the page's margin and the
	   pane sits flush inside its column, so the first gear card starts on the
	   same vertical as the tab above it and the compartment head above that.
	   The 32px above and 40 below are the design's own shell padding. */
	.pad { padding: 32px 0 40px; }
	.geartop { grid-template-columns: minmax(0, 1fr) 212px; gap: 32px; }
}

/* ==========================================================================
   THE LEDGER, contents
   ========================================================================== */

/* Group heading. A rule under it rather than a box around it: the panel is
   already a column, and a second border inside one is a box in a box. */
.lgroup {
	margin: 20px 0 10px;
	padding-bottom: 6px;
	border-bottom: 1px solid var(--line);
	font-family: var(--font-display);
	font-size: 11px;
	font-weight: 600;
	letter-spacing: .16em;
	text-transform: uppercase;
	color: var(--muted);
}
.lgroup:first-child { margin-top: 0; }

/* The headline. Large, plain white, with a leader rule to its figure, because
   a total is the answer and colour on this panel names sources rather than
   values. */
.lead { display: flex; align-items: baseline; gap: 10px; margin-bottom: 8px; }
.lead .k { font-size: 13.5px; color: var(--muted); }
.lead .lead-rule { flex: 1; height: 1px; background: var(--line); align-self: center; }
.lead .v {
	font-family: var(--font-data);
	font-size: 22px;
	line-height: 1.1;
	letter-spacing: -.015em;
	font-variant-numeric: tabular-nums;
	color: var(--ink);
}

/* Where the headline came from. One row per source, its share on the right. */
.prov { display: flex; flex-direction: column; gap: 2px; margin: 0 0 16px; }
.prov div { display: flex; align-items: baseline; gap: 10px; font-size: 12.5px; padding: 2px 0; }
.prov .src { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.prov .amt {
	margin-left: auto;
	font-family: var(--font-data);
	color: var(--muted);
	font-variant-numeric: tabular-nums;
	flex: 0 0 auto;
}
/* The facet chip, laid out as a provenance row. It carries the same classes it
   does anywhere else in the panel, so its colour comes from the one place that
   defines what a facet colour is. */
.prov .facet {
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/* A control that is not built yet, saying so. Same corner and same shape as
   the dropped-content notice, so the two read as one kind of message. */
#unbuiltNotice {
	position: absolute;
	left: 20px; right: 20px; bottom: 14px;
	z-index: 30;
	padding: 12px 16px;
	background: var(--surface-2);
	border: 1px solid var(--line);
	border-left: 2px solid var(--accent);
	border-radius: 10px;
	font-size: 13.5px;
	color: var(--muted);
	max-width: 620px;
	margin-inline: auto;
}

/* ==========================================================================
   CHARM PANEL, TAB STRIP, AUX CARDS
   ========================================================================== */

/* The charm board is a panel, not a loose grid. It is the one part of the gear
   pane with its own coordinate system, and a border is what says so. */
.charmcol .charmgrid {
	padding: 8px;
	background: var(--ground);
	border: 1px solid var(--line);
	border-radius: 10px;
}
.cgfoot {
	margin: 8px 0 0;
	font-family: var(--font-data);
	font-size: 11.5px;
	line-height: 1.5;
	color: var(--muted);
	max-width: 212px;
}

/* Relic and socket cells carry three lines now, like a gear card. */
.slot.aux .sn {
	font-family: var(--font-display);
	font-size: 10px;
	font-weight: 600;
	letter-spacing: .13em;
	text-transform: uppercase;
	color: var(--muted);
	display: block;
	line-height: 1.4;
}
.slot.aux .it { display: block; line-height: 1.35; }

@media (min-width: 1000px) {
	/*
	 * The tab strip: compact and left aligned, not four equal thirds of the
	 * window with the icon stacked over the label.
	 *
	 * Spread across the full width, the four tabs read as four destinations of
	 * equal weight, and the strip took 58px of height to say four words. The
	 * design puts them at the left in reading order, icon beside label, which
	 * is what makes the row read as a set of views on one thing.
	 */
	#tabs {
		/* Flex, not the four equal grid tracks the phone layout uses. The strip
		   spread the four tabs across the full 984 and they read as four
		   destinations of equal weight; the design puts them at the left in
		   reading order so the row reads as four views of one thing. A
		   justify-content on a grid of four 1fr tracks does nothing, which is
		   why this was still spread after the strip was told to start left. */
		display: flex;
		justify-content: flex-start;
		gap: 4px;
		/* Flush with the column. The 46px here was the other half of the pane
		   padding that stood in for the missing gutters, and it put the first
		   tab 46px right of the compartment head above it. */
		padding-left: 0;
	}
	#tabs button {
		flex: 0 0 auto;
		flex-direction: row;
		align-items: center;
		gap: 7px;
		padding: 0 16px;
		width: auto;
		/* 38, the design's, overriding the 44px floor the phone layout sets.
		   That floor is a finger target and this strip is only ever laid out
		   this way for a pointer. */
		min-height: 38px;
		height: 38px;
	}
	#tabs .ti { margin: 0; width: 15px; height: 15px; }
}

/* Socket chips. The rune's name, in the rune colour, in socket order, inside
   the piece that holds them. Order is what makes a runeword, so they are laid
   out as a sequence and never wrapped into a grid that could reorder them. */
.sockets { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; padding: 0 2px; }
.sk {
	font-family: var(--font-data);
	font-size: 10.5px;
	line-height: 1;
	padding: 4px 6px;
	border-radius: 3px;
	border: 1px solid var(--r-rune);
	color: var(--r-rune);
	background: none;
	white-space: nowrap;
}
.sk:hover { background: color-mix(in srgb, var(--r-rune) 14%, transparent); }
.sk.free { border-style: dashed; border-color: var(--line); color: var(--muted); }
.sockets .sockhint {
	font-family: var(--font-display);
	font-size: 10px; font-weight: 600;
	letter-spacing: .13em; text-transform: uppercase;
	color: var(--muted);
}

/* ==========================================================================
   MASTHEAD CONTROLS, as one object each
   ==========================================================================
   A label floating beside a raw native select reads as two things, and the
   select brings its own OS chrome with it: at 1920 the row was a line of
   loose words with three different box styles in it. Each control becomes one
   bordered chip with the label as a segment of it, which is what makes the
   row read as instrument panel rather than form.

   The arrow is drawn rather than inherited. `appearance: none` removes the
   platform one, and without a replacement a select is indistinguishable from
   a text field. */
#bar .ctl {
	gap: 0;
	height: 34px;
	border: 1px solid var(--line);
	border-radius: 6px;
	background: var(--ground);
	overflow: hidden;
}
#bar .ctl label, #bar .ctl .lbl {
	display: flex;
	align-items: center;
	height: 100%;
	padding: 0 10px;
	font-family: var(--font-display);
	font-size: 10px;
	font-weight: 600;
	letter-spacing: .13em;
	border-right: 1px solid var(--line);
	white-space: nowrap;
}
#bar .ctl select, #bar .ctl input {
	height: 100%;
	min-height: 0;
	border: 0;
	border-radius: 0;
	background: none;
	padding: 0 10px;
	font-size: 14px;
}
#bar .ctl select {
	appearance: none;
	padding-right: 26px;
	background-image:
		linear-gradient(45deg, transparent 50%, var(--muted) 50%),
		linear-gradient(135deg, var(--muted) 50%, transparent 50%);
	background-position: calc(100% - 14px) center, calc(100% - 9px) center;
	background-size: 5px 5px, 5px 5px;
	background-repeat: no-repeat;
	cursor: pointer;
}
/* 64: three digits in the data face plus the spinner Chrome draws inside the
   field. At 54 a level of 100 was clipped to "10" and half a nought. */
#bar .ctl input { width: 64px; text-align: right; }
#bar .ctl:focus-within { border-color: var(--accent-dim); }
/* The hero level read-out is a control-shaped chip like the rest of the row,
   so a derived figure and an editable one are visibly different things without
   being different sizes. */
#bar #heroCtl { background: none; }
#bar #heroCtl .tag { border: 0; background: none; min-height: 0; height: 100%; }

/* Buttons match the chips' height, so nothing in the row is a different size.
   min-height as well as height: the base .btn rule sets a 38px minimum for a
   touch target, and a height alone loses to it. The 44px guidance is for a
   finger; this row is pointer-only, the strip a phone actually uses is the
   site nav, and it keeps its own size. */
#bar .btn { height: 34px; min-height: 34px; padding: 0 12px; }

/* A charm in the build that cannot legally sit on the board. Should never
   appear; loud when it does, because the alternative is a charm contributing
   to every total while occupying no cell you can see. */
.cgbad {
	margin: 6px 0 0;
	max-width: 212px;
	font-family: var(--font-data);
	font-size: 11.5px;
	line-height: 1.5;
	color: var(--danger-text);
}

/* A gear cell sizes to its own content, and the row grows to fit it.
   Without this the wrap stretched to whatever height the row had already been
   given, and a slot with sockets overflowed silently into the cell below: the
   belt's socket chips were drawn underneath Ring II's card, so clicking a
   socket opened the ring's picker and equipped a ring instead. Overflow that
   only steals clicks is the worst kind, because the layout still looks right. */
.slotwrap { align-self: start; }

/* The doll cells, mapped from the slot's own name, and only inside the query
   where grid-template-areas is declared. Below this width there is no doll and
   the wraps flow normally, which is what the two-column layout wants. */
@container pane (min-width: 700px) {
	.slotwrap[data-doll="amulet"]      { grid-area: amulet; }
	.slotwrap[data-doll="helmet"]      { grid-area: helmet; }
	.slotwrap[data-doll="weapon_main"] { grid-area: weapon_main; }
	.slotwrap[data-doll="chest"]       { grid-area: chest; }
	.slotwrap[data-doll="offhand"]     { grid-area: offhand; }
	.slotwrap[data-doll="gloves"]      { grid-area: gloves; }
	.slotwrap[data-doll="belt"]        { grid-area: belt; }
	.slotwrap[data-doll="ring_1"]      { grid-area: ring_1; }
	.slotwrap[data-doll="boots"]       { grid-area: boots; }
	.slotwrap[data-doll="ring_2"]      { grid-area: ring_2; }
}

/*
 * The consent banner must not sit over anything you can click.
 *
 * It is `position: fixed`, so it takes no space and the pane happily scrolls
 * content underneath it. That was survivable while the planner was short; the
 * compartment head added a row, and at 1280x900 the relic row ended up beneath
 * the banner. A click aimed at a relic landed on the banner's privacy link and
 * navigated away from the planner entirely, which is a worse failure than
 * anything the banner is there to prevent.
 *
 * So while it is showing, the pane reserves its height at the foot of its own
 * scroll area AND insets its scrollport by the same amount. The padding is
 * what stops the last row being permanently unreachable; the scroll-padding
 * is what stops anything scrolled into view landing under the banner, which
 * is the half that actually bit: a browser scrolls an element to just barely
 * visible, and just barely visible is exactly where the banner is.
 *
 * 132px is the banner at its tallest, which is the two-line wrapping it does
 * at the narrowest width it is shown at, plus its lift.
 */
/* Desktop: reserve the banner's height in the app itself, so the pane's own
   viewport ends above it and nothing is ever underneath. Padding the pane only
   made the last row reachable by scrolling, which is not the same thing: a
   browser scrolls an element to just barely visible, and just barely visible is
   exactly where the banner is. On a phone the banner already clears the bottom
   chrome through --consent-lift, and shortening the app there would lift the
   tab bar off the edge of the screen for no gain. */
@media (min-width: 1000px) {
	/* Scoped to body, not #app: the banner is a sibling of #app, not a child,
	   so `#app:has(#consent)` can never match and silently did nothing. */
	body:has(#consent:not([hidden])) #app { padding-bottom: 132px; }
}

/*
 * The site strip wraps on a phone rather than scrolling.
 *
 * It carries seven sections now instead of three, and at 390px they do not fit
 * on one line. Letting it scroll sideways is exactly how the reference pages
 * became invisible in the first place: a link past the right edge, on a strip
 * with no scroll affordance, is a link nobody knows exists. Two lines of links
 * everyone can see beats one line with four of them hidden.
 *
 * The row becomes auto-height to match, and the separators are dropped: a
 * vertical rule between groups is a horizontal-reading device and it lands
 * mid-wrap as a stray tick.
 */
@media (max-width: 760px) {
	#app { grid-template-rows: var(--bar) auto minmax(0, 1fr) auto var(--anchor) var(--tabs); }
	#sitenav {
		height: auto;
		flex-wrap: wrap;
		row-gap: 0;
		padding-bottom: 2px;
		overflow-x: visible;
	}
	/* 44, not 38. This is the strip a phone reaches for most and it is the one
	   row on the site that must hold the touch minimum; the 38px figure was
	   left over from when the strip was a single non-wrapping line whose height
	   was the thing under pressure. It wraps now, so a taller link costs a
	   second row of nothing. */
	#sitenav a { height: 44px; }
	#sitenav .navsep { display: none; }
	/* Relics go to one column, as the design's ladder does. Three 118px cards on
	   a 390px screen clipped the word "Empty" off the bottom of every one of
	   them: a relic card carries three lines and there is no width at which
	   three of them fit across a phone. */
	.slots.aux.relics, .slots.aux { grid-template-columns: minmax(0, 1fr); }
}


/* ==========================================================================
   MATCHING design-g
   ========================================================================== */

@media (min-width: 1000px) {
	:root {
		/* The masthead carries a mark, a wordmark and a tagline stacked beside
		   it now. 52 was its height when it carried a wordmark alone, and the
		   tagline had been living on the slack that left. */
		--bar: 64px;
		/* 43, which is the design's 42px strip plus the hairline under it: the
		   design puts that rule outside the row's height and this grid puts it
		   inside, so 42 here gives the links 41. This is the only place --nav is
		   set at desktop. */
		--nav: 43px;
	}
	/* 30px, the design's. It was 24 in the planner and 26 on the reference
	   pages, so the one element that carries the class hue was three sizes
	   across a site that is meant to be one. */
	#bar .logomark { width: 30px; height: 30px; flex: 0 0 30px; }
}

/* A charm tile: art and rarity, and nothing else.
 *
 * The tile has one row now. The name used to hold a second one, and that single
 * decision caused all three reported charm defects rather than the one it looks
 * like: it starved the art on small footprints, it was dropped outright below
 * 2 cells so Bob's Grenade rendered with no text, and it bent the art's scaling
 * everywhere. spriteFit is told the box's aspect and decides from it which side
 * limits the sprite; a reserved name row meant the box was never the shape the
 * caller claimed, so the decision was made against a number that was not true.
 *
 * Borrowed from the hybrid direction, which does not label its tiles at all.
 * Identity is the art, rarity is a bar on the top edge, and the name, footprint
 * and stats live in the tooltip that a charm tile already opens on hover. That
 * removes the problem instead of arranging it more carefully.
 */
.cg.filled {
	grid-template-rows: minmax(0, 1fr);
	position: relative;
}
.cg.filled .ico { width: 100%; height: 100%; min-height: 0; }

/* Rarity, on the top edge.
 *
 * Offsets are zero, not negative. `overflow: hidden` clips to the PADDING box,
 * so a bar at -3px sits in the border area, entirely outside that box, and is
 * removed in full. The tile's 3px of padding insets the bar from the border,
 * which is the difference between this and hybrid's full-bleed version and is
 * not worth changing the tile's geometry for: adding a real border-top would
 * take 3px off the art's height and break the footprint aspect charmBoxAspect
 * depends on.
 *
 * --rr is the shared rarity variable every other container already reads off
 * [data-r], so charms pick up the same palette as gear with no new colours. */
.cg.filled[data-r]::after {
	content: '';
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	height: 3px;
	border-radius: 2px;
	background: var(--rr, transparent);
	pointer-events: none;
}

/*
 * THE CLASS HUE, everywhere the design puts it.
 */
.mrow.al {
	border-left: 2px solid var(--class);
	background: var(--class-wash);
	padding-left: 8px;
	margin-left: -10px;
}
.mrow .mv b { color: var(--class); font-weight: 600; }
.mrow button:hover:not(:disabled) { border-color: var(--class); color: var(--class); }
/* An allocated skill, in its icon and its rank, for the same reason. */
.skill.al .si { border-color: var(--class); }
.skill.al .rank b { color: var(--class); }
/* The rail's rule picks the hue up at its left end, matching the masthead's. */
#ledgerHead .rule { background: linear-gradient(90deg, var(--class-dim), var(--line) 40%); }

/* The `.tiny` special case that lived here is gone with the label it hid. No
   charm tile carries text now, so a tile below two cells needs no exception. */

/* The site strip sits on the page floor, not on a second slab of surface.
   design-g puts it on --void, and the reference pages already do; the planner
   was the odd one out. Two filled rows stacked is the grey blob this direction
   exists to remove, and the strip is the second of them. */
@media (min-width: 1000px) {
	#sitenav { background: var(--ground); }
}

/* ---------- masteries, inline under their skill ----------
   The disclosure is the skill's own row extended, not a panel: it sits inside
   the same indent as the skill so a chain still reads down the left edge. */
details.masts {
	margin: 0 0 6px calc(var(--d, 0) * 18px + 34px);
	border-left: 1px solid var(--line);
	padding-left: 12px;
}
details.masts > summary {
	display: flex;
	align-items: center;
	gap: 10px;
	padding: 5px 0;
	cursor: pointer;
	list-style: none;
	color: var(--muted);
}
details.masts > summary::-webkit-details-marker { display: none; }
details.masts > summary::before {
	content: "";
	width: 0; height: 0;
	border: 4px solid transparent;
	border-left-color: var(--muted);
	transition: transform .15s;
}
details.masts[open] > summary::before { transform: rotate(90deg) translateX(-2px); }
.mlabel {
	font-family: var(--font-display);
	font-size: 10px;
	font-weight: 600;
	letter-spacing: .14em;
	text-transform: uppercase;
}
details.masts .mcount {
	margin-left: auto;
	font-family: var(--font-data);
	font-size: 11px;
	color: var(--muted);
}
.mgroup + .mgroup { margin-top: 10px; }
.mhead {
	display: block;
	margin-bottom: 5px;
	font-family: var(--font-display);
	font-size: 10px;
	font-weight: 600;
	letter-spacing: .14em;
	text-transform: uppercase;
	color: var(--muted);
}
/* Two across, because a sub-talent is a name and a stepper and fourteen of
   them in one column is a scroll rather than a tree. */
.mgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 2px 12px; }
.mgrid .mrow { border-top: 0; padding: 4px 0; }

/* ==========================================================================
   THE PLANNER IS A PAGE ON THIS SITE

   Everything below restates the layout, and it is stated last on purpose:
   the rules above describe a full-height application frame, and this file has
   too many of them for editing each in place to be trustworthy. One block, at
   the end, that wins.

   WHAT WAS WRONG. The planner was not built like the rest of the site. It was
   a 100dvh grid: the masthead, the site strip, the panes and the Ledger were
   all cells of one frame, that frame was capped at 1600px and centred, and the
   advertising column sat on its LEFT. Three things followed from that and all
   three were visible.

   The masthead and the site strip stopped at the frame's edges, so the two
   bands floated in the middle of the window with the page ground showing down
   both sides, where on every other page they run the full width of the glass.

   The tool's content sat in a middle lane with a margin either side of it that
   held nothing at all.

   And the panes scrolled inside the frame rather than the page scrolling, so
   the planner did not even behave like a page.

   WHAT IT IS NOW. design-g's own pattern, which is the pattern every reference
   page on this site already used: a band element that spans the window, with a
   container inside it holding the content to the measure. The document scrolls.
   The shell is pillar, workspace, Ledger, which is the design's arrangement and
   the owner's: the two outer columns carry the advertisement and the numbers,
   and neither of them is a margin.
   ========================================================================== */

html, body {
	height: auto;
	overflow: visible;
}

#app {
	display: block;
	height: auto;
	width: auto;
	max-width: none;
	margin: 0;
	padding: 0;
	overflow: visible;
}

/* The bands. Full width, each carrying its own ground, exactly as
   header.bar and nav.sitenav do on every generated page. */
#bar, #sitenav {
	display: block;
	width: 100%;
	max-width: none;
	margin: 0;
	padding: 0;
	overflow: visible;
	box-shadow: none;
	clip-path: none;
}
#bar { background: var(--surface); position: relative; }
#sitenav { background: var(--ground); border-bottom: 1px solid var(--line); }
/* The fixed stripes that stood in for a full-width band are gone: the band is
   the element again, so there is nothing left to fake. */
#bar::before, #sitenav::before { content: none; }

/* The container. One measure, used by the masthead, the site strip and the
   page shell, so every row on the planner starts on the same vertical and on
   the same vertical as every other page. */
.wrap {
	max-width: var(--max);
	margin: 0 auto;
	padding: 0 var(--pad);
	width: 100%;
}
#bar > .wrap {
	display: flex;
	align-items: center;
	gap: 10px;
	min-height: var(--bar);
}
#sitenav > .wrap {
	display: flex;
	align-items: stretch;
	gap: 24px;
	min-height: var(--nav);
}
#sitenav > .wrap > a { padding: 0; }

/* The shell. One column on a phone; the Ledger joins it when there is room for
   it; the advertising column joins last, because it is the one carrying nothing
   the reader came for. */
.pageshell {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: var(--gut);
	align-items: start;
	padding-top: 32px;
	padding-bottom: 64px;
}
.work { min-width: 0; }

@media (min-width: 981px) {
	.pageshell { grid-template-columns: minmax(0, 1fr) var(--ledger); }
	.pageshell > .work { grid-column: 1; }
	.pageshell > #summary { grid-column: 2; }
	.pageshell > #adPillar { display: none; }
}
@media (min-width: 1241px) {
	.pageshell { grid-template-columns: var(--pillar) minmax(0, 1fr) var(--ledger); }
	/* By id. `#adRail, #adPillar { display: none }` further up is an id
	   selector, and a class rule here loses to it however late it is written:
	   the column was declared, the element was hidden, and the shell drew a
	   160px hole. */
	.pageshell > #adPillar {
		display: block;
		grid-column: 1;
		position: sticky;
		top: 32px;
		background: none;
		border: 0;
	}
	.pageshell > .work { grid-column: 2; }
	.pageshell > #summary { grid-column: 3; }
}

/* The panes are in the document now, not stacked inside a fixed frame. */
#panes { position: static; overflow: visible; }
/*
 * `relative`, not `static`.
 *
 * The pane used to be `position: absolute; inset: 0` inside a fixed-height
 * frame, and three things inside it are absolutely positioned against it: the
 * board's own box, the failure overlay, and the not-built-yet notice. Making
 * the pane static took their containing block away, so they resolved against
 * the viewport instead and painted over the entire window. That is the black
 * rectangle over the whole screen on the Incarnation tab.
 */
.pane {
	position: relative;
	inset: auto;
	overflow: visible;
	display: none;
}
.pane.on { display: block; }
.pane.flush { overflow: visible; }
.pad { padding: 0; }

/*
 * The board's box, and why it is no longer absolute.
 *
 * `position: absolute; inset: 0` gave it the pane's full height back when the
 * pane was a cell of a 100dvh frame. In a document the pane is as tall as its
 * contents and the contents are the board, so inset:0 is circular: it asks for
 * the height of a box whose height it is. It has an explicit height instead,
 * and the canvas is sized from it by the ResizeObserver that already watches
 * this element, which is also what makes it correct on the FIRST click rather
 * than after switching away and back: the observer fires when the pane stops
 * being display:none and the box gains a size.
 */
#treeWrap {
	position: relative;
	inset: auto;
	width: 100%;
	height: min(74vh, 760px);
	overflow: hidden;
	border: 1px solid var(--line);
	border-radius: 10px;
	background: var(--ground);
}

/* The Ledger is a rail that follows the reader down a long page. It is no
   longer a cell with its own scrollbar: the page scrolls, so it sticks. */
#summary {
	position: sticky;
	top: 32px;
	background: none;
	border: 0;
	overflow: visible;
	min-height: 0;
}
#summaryBody { max-height: none; padding: 0; overflow: visible; }
#summaryToggle { display: none; }

/* The tab strip is a row of the workspace, not a bar at the foot of a window. */
#tabs {
	display: flex;
	position: static;
	justify-content: flex-start;
	gap: 4px;
	padding: 0;
	background: none;
	border-top: 0;
	border-bottom: 1px solid var(--line);
	margin-bottom: 24px;
}
#tabs button {
	flex: 0 0 auto;
	flex-direction: row;
	align-items: center;
	gap: 7px;
	width: auto;
	height: 38px;
	min-height: 38px;
	padding: 0 16px;
	border-top: 0;
	border-bottom: 2px solid transparent;
}
#tabs button.on { border-bottom-color: var(--accent); border-top-color: transparent; }
#tabs .ti { margin: 0; width: 15px; height: 15px; }

/* The compartment head sits above the strip, flush with the column. */
#workbench {
	display: flex;
	align-items: center;
	gap: 14px;
	height: 26px;
	padding: 0;
	margin: 0 0 14px;
}
#ledgerHead { display: flex; }


/* On a phone the strip scrolls sideways rather than wrapping to two rows, and
   the tabs keep their 44px finger target. */
@media (max-width: 980px) {
	#tabs { overflow-x: auto; scrollbar-width: none; }
	#tabs::-webkit-scrollbar { display: none; }
	#tabs button { height: 44px; min-height: 44px; }
	#summary { position: static; }
	#treeWrap { height: min(66vh, 560px); }
}

/* ==========================================================================
   FOUR THINGS THE OWNER MEASURED BY EYE AND WAS RIGHT ABOUT
   ========================================================================== */

/*
 * 1. GEAR CARDS ARE ONE HEIGHT.
 *
 * They were sized by their contents, so a card holding three gems stood a
 * hundred pixels taller than the empty one beside it and the doll came out
 * ragged: an amulet with one socket next to a helmet with three named gems
 * stacked in it. Filling sockets made it worse, which is the wrong direction
 * for a tool whose whole job is filling sockets.
 *
 * `grid-auto-rows: 1fr` makes every row of the doll the height of its tallest
 * card and the wrappers stretch into it, so the silhouette is fixed and adding
 * a gem changes what is in a card and never where the card ends.
 */
.slots { grid-auto-rows: 1fr; }
.slots > .slotwrap { height: 100%; }
.slotwrap > .slot { flex: 1; }

/*
 * 2. THE LEDGER SITS ON THE WORKBENCH'S BASELINE.
 *
 * Two compartment heads, one either side of the workspace, and they were 14px
 * apart: the Ledger's head carried a top margin the Workbench's did not. They
 * are a pair and they have to start on one line, or the two columns read as two
 * unrelated panels that happen to be next to each other.
 */
#workbench, #ledgerHead {
	height: 26px;
	margin: 0 0 14px;
	padding: 0;
}

/*
 * 3. "RAISE THIS" IS A CHIP IN A COLUMN, NOT A PILL WHEREVER THE LABEL ENDS.
 *
 * It sat immediately after the stat's name, so its left edge moved with the
 * length of every label and a column of them zig-zagged down the panel. Pushed
 * to the end of the name's own box it lands in one column, immediately left of
 * the figures, on every row that has one.
 *
 * Square, not a 999px pill. Nothing else on this site is a lozenge, and a shape
 * used once is a shape that reads as a mistake.
 */
.stat .head .nm { display: flex; align-items: baseline; gap: 8px; min-width: 0; flex: 1; }
.stat .head .nm > .lbltext { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.stat .ask {
	margin-left: auto;
	border-radius: 3px;
	padding: 1px 6px;
	font-size: 10px;
	letter-spacing: 0.1em;
}

/*
 * 4. A RELIC CARD IS THE SIZE OF WHAT IS IN IT.
 *
 * It inherited the gear card's 78px floor and its 12px padding, and a relic
 * carries a kicker and one line: the result was a card with more empty space in
 * it than content, five times over, directly under a doll of full ones.
 */
.slots.aux .slot, .slot.aux {
	min-height: 0;
	padding: 10px 12px;
	gap: 10px;
}
.slots.aux { grid-auto-rows: auto; }
.slots.aux .ico { width: 34px; height: 34px; flex: 0 0 34px; }

/*
 * THE SOCKET STRIP IS ONE LINE.
 *
 * Uniform card height fixed the ragged doll and left a second problem behind:
 * every card was then as tall as the tallest, and the tallest was a helmet with
 * three gems in it. Gem names are long ("Death's Soulgem" is fifteen
 * characters), so three of them wrapped to three lines and set every card in
 * the doll to 178px. Four rows of that is seven hundred pixels of doll.
 *
 * One line, scrolling sideways in place. The design's own socket row is one
 * line; it got away with it because runes are three letters. Nothing is hidden
 * and nothing is truncated: the strip scrolls, so a six-socket weapon is read
 * by dragging the strip, and the card is 102px whatever is in it.
 */
.slotwrap > .sockets,
.slot .socks {
	flex-wrap: nowrap;
	overflow-x: auto;
	overflow-y: hidden;
	scrollbar-width: none;
	overscroll-behavior-x: contain;
}
.slotwrap > .sockets::-webkit-scrollbar,
.slot .socks::-webkit-scrollbar { display: none; }
.sk { flex: 0 0 auto; }
.sockets .sockhint, .sockets .rwbadge, .sockets .rwhint { flex: 0 0 auto; }

/* Relic cards are one height too, for the same reason the gear cards are. */
.slots.aux.relics { grid-auto-rows: 1fr; }
.slots.aux.relics > .slot { height: 100%; }

/* The site footer, written to pages.css's own numbers so the planner's foot and
   a reference page's foot are one component. */
footer {
	margin-top: 40px;
	padding: 22px 0 64px;
	border-top: 1px solid var(--line);
	color: var(--muted);
	font-size: 14px;
}
footer p { margin: 0 0 8px; max-width: 78ch; }
footer .build { font-family: var(--font-data); font-size: 12px; color: var(--line-2, var(--muted)); }
footer ul.inline {
	display: flex;
	flex-wrap: wrap;
	gap: 18px;
	margin: 0 0 12px;
	padding: 0;
	list-style: none;
}
footer a, footer .linklike { color: var(--muted); text-decoration: underline; }
footer a:hover, footer .linklike:hover { color: var(--ink); }

/*
 * ROOM FOR THE CONSENT BANNER, restated.
 *
 * The banner is a fixed overlay, so it covers whatever the document ends with.
 * There was a rule reserving its height, and the block that turned the planner
 * into a page set `#app { padding: 0 }` after it, which silently took the
 * reservation away: the last row of relics ended up underneath the banner and
 * a click on it hit the banner instead. That is the same fault the site owner
 * reported once already, reintroduced by a later rule of equal specificity.
 *
 * On the document rather than on #app this time, so nothing that changes #app
 * can take it away again. 132px is the banner at its tallest, which is the two
 * line wrapping it does at the narrowest width it is shown at, plus its lift.
 */
body:has(#consent:not([hidden])) { padding-bottom: 132px; }
/*
 * And scroll-padding, which is the half that actually bites.
 *
 * End padding stops the last row being unreachable. It does NOT stop a browser
 * scrolling something to just-barely-visible and landing it under the banner,
 * and just-barely-visible is exactly where the banner is: that is what made a
 * locked relic unclickable while its own hit test said it was fine. Scroll
 * padding insets the scrollport, so anything scrolled or focused into view
 * stops above the banner instead of behind it.
 */
html:has(#consent:not([hidden])) { scroll-padding-bottom: 148px; }

/*
 * THE PHONE MASTHEAD, retargeted at the container.
 *
 * The bands hold their contents in a `.wrap` now, so the flex row is the wrap
 * and not the band. Every phone rule was still written against the band, which
 * meant "wrap the seven site links onto two rows" applied to an element that no
 * longer laid them out: at 390px the row stayed on one line, ran to 811px, and
 * took the whole document sideways with it.
 */
@media (max-width: 980px) {
	#bar > .wrap { flex-wrap: wrap; row-gap: 6px; padding-top: 8px; padding-bottom: 8px; }
	#bar { min-height: 0; }
	/* The character chips wrap onto a second row rather than running off the
	   side. The bar used to hide the overflow and scroll sideways, which is
	   exactly how the site's own navigation became invisible once before: a
	   control past the right edge, on a row with no scroll affordance, is a
	   control nobody knows is there. Three chips are 464px and a phone is 390,
	   so they were taking the whole document sideways with them. */
	/* `flex: 1 1 auto` and a cap, not the base rule's `flex: none`. With
	   `flex: none` the group sizes to max-content, so it stayed 464px wide
	   and its children never had a reason to wrap: flex-wrap was set and
	   correct and did nothing, because nothing was ever short of room. */
	.ctlgroup { flex: 1 1 auto; flex-wrap: wrap; row-gap: 6px; min-width: 0; max-width: 100%; }
	.ctlgroup .ctl { flex: 0 1 auto; min-width: 0; }
	#bar .spacer { display: none; }
	#sitenav > .wrap {
		flex-wrap: wrap;
		gap: 2px;
		row-gap: 0;
		min-height: 0;
		/* Less the 11px each link takes back below, so the first link's text
		   still lands on the page's left edge. */
		padding-left: calc(var(--pad) - 11px);
		padding-right: calc(var(--pad) - 11px);
	}
	#sitenav > .wrap > a { padding: 0 11px; min-height: 44px; height: auto; }
	#sitenav .navsep { display: none; }
}
/* The character chips are the tool's own controls and stay on the planner at
   every width; the wordmark gives way to them instead, as it always did. */
@media (max-width: 560px) {
	#bar > .wrap { gap: 7px; }
}

/* ==========================================================================
   THE SOCKET CHIP, at design-g's size

   The design's chip is 10px mono with 3px by 5px of padding and it WRAPS
   (design-g.css: `.socks{display:flex;gap:3px;margin-top:5px;flex-wrap:wrap}`).
   Ours was 10.5px with 4px by 6px and it did not wrap, so three of them ran off
   the side of the card; making it scroll sideways to hide that was a worse
   answer than the problem.

   The design got away with one line because everything it shows is a rune, and
   a rune is called Ol or Tor or Old: 41 of the 128 socketables in the client
   are two or three characters. The other 87 are gems and jewels with real
   names, so a card here has to survive "Death's Soulgem" three times. At the
   design's metrics two of those fit a line and three sockets is two lines, so
   the worst realistic card is 124px rather than 178, nothing is truncated, and
   nothing is hidden behind a scroll.
   ========================================================================== */
.slotwrap > .sockets,
.slot .socks {
	display: flex;
	flex-wrap: wrap;
	gap: 3px;
	margin-top: 5px;
	overflow: visible;
}
.sk {
	flex: 0 0 auto;
	font-family: var(--font-data);
	font-size: 10px;
	line-height: 1;
	padding: 3px 5px;
	border-radius: 3px;
	border: 1px solid var(--r-rune);
	color: var(--r-rune);
	letter-spacing: .02em;
	white-space: nowrap;
}
.sk.free { border-style: dashed; border-color: var(--line-2, var(--line)); color: var(--muted); }

/* ==========================================================================
   THE SOCKETS SECTION
   Its own compartment beside Relics, because a socket needs a row's width and
   a card only has 161 pixels of it.
   ========================================================================== */

/* Cards size to their contents again, as the design's do: 79, 102 and 123 in
   design-g, because an amulet with no sockets is not the same object as a
   weapon with five. Forcing them all to the tallest was a fix for the socket
   strip overflowing, and the strip does not overflow any more. */
.slots { grid-auto-rows: auto; }
.slots > .slotwrap { height: auto; }

/* The strip on the card. Inside the frame, under the base line. */
.slotwrap > .socks { padding: 0 12px 12px 68px; margin: -2px 0 0; }

.sockrows { display: flex; flex-direction: column; gap: 6px; margin-bottom: 8px; }
.sockrow {
	display: grid;
	grid-template-columns: minmax(120px, 200px) minmax(0, 1fr) auto;
	align-items: center;
	gap: 16px;
	padding: 10px 12px;
	background: var(--surface);
	border: 1px solid var(--line);
	border-left: 2px solid var(--line-2, var(--line));
	border-radius: 10px;
}
.sockrow[data-r] { border-left-color: var(--rr, var(--line)); }
.sockwho { min-width: 0; }
.sockwho .sn {
	display: block;
	font-family: var(--font-display);
	font-size: 10px;
	font-weight: 600;
	letter-spacing: .14em;
	text-transform: uppercase;
	color: var(--muted);
	line-height: 1.4;
}
.sockwho .it {
	display: block;
	font-size: 13.5px;
	color: var(--rr, var(--ink));
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.sockwho .sub3 { display: block; font-size: 11.5px; color: var(--muted); }
.sockcells { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; min-width: 0; }
.sockword { justify-self: end; }
@media (max-width: 760px) {
	.sockrow { grid-template-columns: minmax(0, 1fr); gap: 8px; }
	.sockword { justify-self: start; }
}

/*
 * ============================================================================
 * THE ITEM TOOLTIP, rebuilt on the hybrid direction's.
 * ============================================================================
 *
 * Appended rather than edited in place. `#tip` is declared at line 1146 and
 * again at 1862 and 2447, all at equal specificity, so the last one wins and an
 * edit to the first is silently discarded. This block is the last one.
 *
 * This matters more than it did: the charm tile no longer carries a name, so
 * this tooltip is the only place a charm's identity, footprint and stats are
 * readable at all. It has to be worth reading.
 *
 * What changed, and why each one:
 *
 * The value moves to the LEFT and the stat name to the right. Values are the
 * column you scan, and a ragged left edge of names with the numbers pushed to
 * a ragged right edge means the eye crosses the box for every row. Fixed
 * value column, tabular figures, so the digits line up vertically.
 *
 * Values are gold rather than the interface cyan. Cyan is the accent this app
 * uses for controls and focus, and a tooltip full of it reads as a list of
 * things to click.
 *
 * The name takes the rarity colour, and the subtitle becomes monospaced small
 * caps, so the header is an item rather than a heading.
 *
 * Rules separate header from stats and stats from grants, because those are
 * three different kinds of fact and were previously one undifferentiated run.
 */
/* Gold, and its own token rather than a rarity colour reused. A value is not a
   rarity, and borrowing --r-unique would make every number look unique-tier. */
:root { --tip-val: #E8B54B; }

#tip {
	max-width: min(320px, 84vw);
	padding: 12px 14px;
	border-color: var(--line);
	background: rgba(17, 19, 22, 0.98);
}
#tip h3 {
	margin: 0 0 3px;
	font-size: 15.5px;
	font-weight: 600;
	color: var(--ink);
}
#tip h3[data-r] { color: var(--rr, var(--ink)); }
#tip .tier {
	font-family: var(--font-data);
	font-size: 10px;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--muted);
	margin: 0 0 9px;
	padding-bottom: 9px;
	border-bottom: 1px solid var(--line);
}

/* Two columns: the value, then what it is. `order` reverses the source, which
   stays label-then-value so nothing that reads the DOM has to change. */
#tip .row2 {
	display: grid;
	grid-template-columns: minmax(72px, max-content) 1fr;
	gap: 12px;
	align-items: baseline;
	padding: 3.5px 0;
	border-bottom: none;
	font-size: 12.5px;
}
#tip .row2 .vals {
	order: -1;
	justify-content: flex-start;
	font-family: var(--font-data);
	font-variant-numeric: tabular-nums;
}
#tip .row2 .vals b {
	font-family: var(--font-data);
	font-variant-numeric: tabular-nums;
	font-weight: 600;
	color: var(--tip-val);
}
#tip .row2 .src {
	font-size: 12.5px;
	color: var(--muted);
	font-family: var(--font-ui);
}

/* A granted skill is not a stat, so it sits under its own rule with its kind
   as the label and the skill in the mythic purple the rest of the app uses for
   granted things. This is the row relic-granted skills will reuse. */
#tip .row2.grantrow {
	margin-top: 9px;
	padding-top: 9px;
	border-top: 1px solid var(--line);
}
#tip .row2.grantrow + .row2.grantrow {
	margin-top: 0;
	padding-top: 3.5px;
	border-top: none;
}
#tip .row2.grantrow .src {
	order: -1;
	font-family: var(--font-data);
	font-size: 10px;
	letter-spacing: 0.08em;
	text-transform: uppercase;
}
#tip .row2.grantrow .src b {
	display: block;
	margin-top: 2px;
	font-family: var(--font-ui);
	font-size: 12.5px;
	letter-spacing: 0;
	text-transform: none;
	color: var(--r-mythic);
}

/*
 * The tooltip shows all of itself.
 *
 * It used to cap at min(70vh, 520px) and scroll. That scrollbar could never be
 * used: `#tip` is pointer-events:none so the wheel goes to the page underneath,
 * and moving the pointer toward the tip changes what is hovered. So the cap did
 * not defer the content, it hid it, and the bar advertised a control that was
 * not there.
 *
 * The tallest item in the payload is Dev Charm Small at 21 rows, roughly 493px,
 * and nothing else clears 18. So a viewport-height bound is never reached by
 * real data; it stays only so a future item with fifty stats degrades to a
 * scrollbar rather than running off the bottom of the screen. The show handler
 * already flips the tip up when it would cross the lower edge, so the whole box
 * lands on screen at any pointer position.
 */
#tip {
	max-height: calc(100vh - 16px);
	overflow-y: auto;
}

/* "not in totals" is a note, not an alarm. It marks a stat the engine does not
   aggregate, which is unimplemented rather than wrong, and it fires on seven of
   thirteen rows on a real unique. In --danger red that reads as an item full of
   errors. Muted keeps the disclosure and drops the shouting. */
#tip .row2 .src em {
	color: var(--muted);
	font-style: normal;
	font-size: 10px;
	opacity: 0.75;
}

/*
 * An empty socket is a dot, not the word "open".
 *
 * A six-socket weapon printed "open open open open open open" across its card:
 * twenty-four characters saying the same nothing six times, and the loudest
 * text on a card whose item name sat above it. Hybrid draws sockets as dots and
 * that is the right weight for "how many, how many left".
 *
 * Only the EMPTY ones become dots. A filled socket keeps its rune name, because
 * the name is the whole reason the strip is in the card: knowing a piece holds
 * Ol and Tor is what tells you which runeword it spells. The sockets section
 * below still carries the full names and every control.
 */
/* An empty socket is an empty socket, not a dot beside one.
 *
 * At 9px the empties read as punctuation under the filled chips rather than as
 * the same kind of thing, and a six-socket weapon looked like three items and
 * an ellipsis. They take the filled chip's 22px box now, drawn as an empty
 * well: same footprint, same line, obviously vacant. That also makes them a
 * click target the size of the thing they become. */
.socks .sk.free {
	width: 22px;
	height: 22px;
	min-width: 0;
	padding: 0;
	border-radius: 5px;
	font-size: 0;
	line-height: 0;
	color: transparent;
	background: var(--ground);
	border: 1px dashed var(--line);
	vertical-align: middle;
}
.socks .sk.free:hover { border-color: var(--accent-dim); border-style: solid; }

/* The card's third line is a stat now, so it gets the data face and enough
   contrast to be read as a value rather than a caption. */
.slot .sub3 {
	font-family: var(--font-data);
	font-variant-numeric: tabular-nums;
	font-size: 10.5px;
	letter-spacing: 0.02em;
	text-transform: uppercase;
	color: var(--muted);
}

/* A filled socket in the card is its contents' art, sized to sit on the same
   line as the empty sockets' dots without changing the card's height. The
   tooltip still names it on hover and the sockets section still spells it out. */
/* inline-grid, not grid.
 *
 * `.slot .socks` is declared as a flex row twice, at 1319 and 3486, and
 * computes to `display: block` anyway, so neither the gap nor the nowrap is in
 * effect. The strip only looks like a row because `.sk` is inline-block and
 * inline boxes flow. A plain `grid` here is block-level, so every filled socket
 * took a line of its own and the runes ran down the card while the empty dots
 * stayed in a row underneath.
 *
 * Fixed at the chip rather than by making the container flex: that container is
 * shared with the sockets section and is inside the layout the card's height
 * depends on. This keeps the change to the thing I added. */
.socks .sk.art {
	width: 22px;
	height: 22px;
	padding: 0;
	display: inline-grid;
	place-items: center;
	vertical-align: middle;
	background: var(--ground);
}
.socks .sk.art .ico { width: 18px; height: 18px; }
.socks .sk.free { vertical-align: middle; }

/* What a granted skill does, under its name in the item tooltip. Its own line
   and its own weight: the row above is "which skill", this is "what it is". */
#tip .row2.grantrow .src .gwhat {
	display: block;
	margin-top: 3px;
	font-family: var(--font-ui);
	font-size: 11.5px;
	line-height: 1.35;
	letter-spacing: 0;
	text-transform: none;
	color: var(--muted);
}
/* A Ledger grant that can explain itself says so by being pointable. */
.stat.grant[data-gdesc] { cursor: help; }

/*
 * GEAR CARDS ARE ALL ONE SIZE.
 *
 * They were content-sized, so an empty slot came out at 80px, a filled one with
 * a stat line at 94, and a socketed weapon at 130. Ten cards at three heights
 * reads as a ragged wall rather than a doll, and the ragging carried no meaning:
 * a taller card did not mean a better item, only a longer one.
 *
 * Equal rows plus stretch rather than a fixed height. `grid-auto-rows: 1fr`
 * makes every row the height of the tallest, and stretching the card into its
 * row makes every card that height, so the size is derived from the real
 * content instead of a number that goes stale the first time a line is added.
 *
 * `align-self: start` at 2930 is what prevented this. It is overridden here
 * rather than edited there because that rule is inside the doll's container
 * query and also governs the aux grids, which are not meant to stretch.
 */
.slots { grid-auto-rows: 1fr; }
.slots > .slotwrap { align-self: stretch; height: auto; }

/*
 * The runeword a piece spells, on the piece.
 *
 * Back from the deleted sockets section, and only this part of it. Detection
 * never stopped working; it simply had nowhere to appear, and a planner that
 * knows you have spelled Breath of the Damned and says nothing is worse than
 * one that cannot tell, because silence reads as "it did not work".
 *
 * Sized as a note rather than a heading. The runes above it are the thing you
 * are arranging; this is the answer they produce.
 */
/* Indented to the text column, not the card edge.
 *
 * `.rwline` is a sibling of `.slot`, not a child, so it inherits none of the
 * card's inner padding. `.slotwrap > .socks` at 3648 solves the same problem
 * with `padding: 0 12px 12px 68px`, the 68 clearing the art thumbnail, and this
 * line has to agree with it or the runeword sits left of the runes that spell
 * it. The socks rule keeps its own bottom padding; this takes it over when the
 * two stack, so a card with a runeword is not 12px taller than one without. */
.slotwrap > .rwline {
	display: flex;
	align-items: baseline;
	gap: 6px;
	min-width: 0;
	padding: 0 12px 12px 68px;
	margin: -2px 0 0;
}
.slotwrap > .socks:has(+ .rwline) { padding-bottom: 4px; }
/* The pill at 1500 was built for the sockets section's own row, where it had a
   line to itself. In a 130px card it is a bordered box hanging off the edge, so
   the box is unset here and only the word survives. */
.slotwrap > .rwline .rwbadge {
	font-family: var(--font-ui);
	font-size: 11px;
	font-weight: 600;
	letter-spacing: 0;
	padding: 0;
	border: 0;
	border-radius: 0;
	background: none;
	color: var(--r-rune, var(--accent));
	min-width: 0;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
.slotwrap > .rwline .rwlv {
	font-family: var(--font-data);
	font-size: 10px;
	color: var(--muted);
	white-space: nowrap;
}

/*
 * ============================================================================
 * WHERE YOU ARE IS THE CLASS'S COLOUR, and so is every separator.
 * ============================================================================
 *
 * --accent is the interface cyan: controls, focus rings, things you operate. It
 * was also marking the current page and the current tab, so on an Amazon the
 * whole app said green except the two elements telling you where you were, and
 * those said cyan. Position is a fact about the build, not a control, so it
 * takes the class hue like the wordmark and the allocated nodes already do.
 *
 * The rules are the same idea. #ledgerHead already drew its rule as a gradient
 * running out of the class colour into the panel line, and every other rule in
 * the app was a flat grey. One of them was right.
 *
 * Drawn as a background rather than a border, because a border cannot hold a
 * gradient without border-image, and border-image applies to all four sides and
 * then has to be unpicked on three. A 1px background strip pinned to the bottom
 * edge is the same pixel with none of that.
 */

/* Where you are, in the site nav and in the planner's own tabs. */
#sitenav a[aria-current='page'] {
	color: var(--class);
	border-bottom-color: var(--class);
}
#tabs button.on {
	color: var(--class);
	border-top-color: var(--class);
}

/* The Workbench's rule, matching the Ledger's opposite it. Both run out of the
   class colour and fade into the panel line by 40%. */
#workbench .rule {
	background: linear-gradient(90deg, var(--class-dim), var(--line) 40%);
}

/* Every section separator: Offence and Defence in the Ledger, Granted by your
   gear above them, and Relics and Charms in the gear pane. */
.lgroup,
#summaryBody .label.sub,
.slots + .label,
.note + .label {
	border-bottom: 0;
	border-top: 0;
	background-image: linear-gradient(90deg, var(--class-dim), var(--line) 40%);
	background-repeat: no-repeat;
	background-size: 100% 1px;
}
/* .lgroup carries its rule underneath the word; the others carry it above. */
.lgroup { background-position: left bottom; }
#summaryBody .label.sub,
.slots + .label,
.note + .label { background-position: left top; }

/* The tab's underline moves from the top edge to the bottom at desktop widths
   (2218), and that rule sets its own colour, so colouring border-top alone left
   the marker cyan at exactly the width the owner uses. The count beside the
   label is the same signal and was the same miss. */
#tabs button.on { border-bottom-color: var(--class); }
#tabs button[aria-selected='true'] .tcount { color: var(--class); }

/*
 * THE PICKER'S FILTER BAR.
 *
 * Between the search box and the list, and outside the list's scroll, so the
 * filters stay put while the results move. A filter that scrolls away is a
 * filter nobody remembers is on.
 */
#pickerFilters {
	display: flex;
	flex-wrap: wrap;
	gap: 5px;
	padding: 0 14px 10px;
	border-bottom: 1px solid var(--line);
	margin-bottom: 8px;
}
#pickerFilters[hidden] { display: none; }
.pfchip {
	display: inline-flex;
	align-items: baseline;
	gap: 5px;
	padding: 4px 9px;
	border: 1px solid var(--line);
	border-radius: 999px;
	background: var(--ground);
	color: var(--muted);
	font-family: var(--font-data);
	font-size: 11px;
	cursor: pointer;
}
.pfchip:hover { border-color: var(--accent-dim); color: var(--ink); }
.pfchip:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* The chosen filter takes the class hue, like everything else that says where
   you are rather than what you can press. */
.pfchip.on {
	border-color: var(--class);
	color: var(--class);
	background: var(--class-wash);
}
/* The count sits quieter than the shape it counts. */
.pfchip .pfn { font-size: 10px; opacity: 0.65; }
/* A shape that cannot go on the cell you clicked. Shown, not hidden, for the
   same reason those rows are shown: knowing it exists is worth something. */
.pfchip.nofit { opacity: 0.4; }
.pfchip.nofit:hover { opacity: 0.7; }

/* The socket-count stepper, sized for a card rather than the section it used to
   live in. 26px buttons and 6px gaps were right on a full-width row; in a 130px
   card they are the loudest thing on it, so they come down to the scale of the
   socket wells they sit beside. */
.socks .sockset {
	padding: 0 4px;
	gap: 3px;
	vertical-align: middle;
	border-color: transparent;
}
.socks .sockset .step {
	width: 18px;
	height: 18px;
	font-size: 12px;
	line-height: 1;
	padding: 0;
}
.socks .sockset b {
	font-size: 11px;
	min-width: 9px;
	color: var(--muted);
}

/*
 * The editor, when it is the dialog's contents.
 *
 * It was built as a panel at the foot of the gear pane, so it carries its own
 * frame and its own header. Inside the dialog that frame is a second box drawn
 * one padding-width inside the first, and the header is a second title beside a
 * second Close. Both are dropped here rather than in the markup, because the
 * editor is one template and this is only true of one of its two homes.
 */
#pickerList .editor {
	margin: 0;
	padding: 0;
	border: 0;
	background: none;
	border-radius: 0;
}
#pickerList .editor .edhead { display: none; }

/* The socket wells inside the crafting dialog. Same cells as the card, given a
   little more room because the dialog is not fighting a 130px card for height. */
.edrow .edsocks {
	display: inline-block;
	margin-left: 8px;
	vertical-align: middle;
}
.edrow .edsocks .sk { margin-right: 3px; }


/* A formed runeword, under the item it was spelled on. Its own ruled block, for
   the same reason grants have one: it is not a property of the base item. */
#tip .rwblock {
	margin-top: 9px;
	padding-top: 9px;
	border-top: 1px solid var(--line);
}
/* Stacked, not side by side. In a 320px tooltip the name and the meta line
   fought for the same row and both wrapped mid-phrase, so "Delirium Tremens"
   broke across two lines beside "VEX VEX" doing the same. */
#tip .rwhead { margin-bottom: 6px; }
#tip .rwhead b { display: block; }
#tip .rwhead .src { display: block; margin-top: 2px; }
#tip .rwhead b { color: var(--r-rune, var(--accent)); font-size: 12.5px; }
#tip .rwhead .src {
	font-family: var(--font-data);
	font-size: 10px;
	letter-spacing: 0.06em;
	text-transform: uppercase;
}

/*
 * The grant group headings.
 *
 * Reuses .lgroup so a grant group looks like Offence and Defence do, but
 * smaller and tighter: these sit inside a section rather than starting one, and
 * at the full size they competed with the heading above them.
 */
#summaryBody .lgroup.gsub {
	margin: 14px 0 6px;
	padding-bottom: 4px;
	font-size: 10px;
	letter-spacing: .12em;
}
#summaryBody .lgroup.gsub:first-of-type { margin-top: 8px; }
#summaryBody .lgroup.gsub .sub2 { letter-spacing: 0; opacity: 0.6; }
/* Rows in a group are tighter than standalone ones: the heading carries the
   kind, so each row is a name and a level rather than a labelled statement. */
#summaryBody .lgroup.gsub + .stat.grant,
#summaryBody .stat.grant + .stat.grant { margin-top: 4px; }
