/* ---------- Theme tokens ---------- */
#ls-app[data-theme="cyber"]{
  --bg:#0b0e17; --panel:#131728; --panel2:#171c30; --border:#232842; --border-hi:#3a3f66;
  --text:#e8eaf2; --muted:#9aa1b8; --accent:#22d3ee; --accent2:#7c3aed;
  --win:#4ade80; --win-wash:rgba(74,222,128,.13); --lose:#5a6180; --glow:0 0 0 1px rgba(34,211,238,.35), 0 0 18px rgba(34,211,238,.16);
  --accent-rgb:34,211,238; --accent2-rgb:124,58,237; --node:#161b2e; --grid:rgba(124,58,237,.07);
}
#ls-app[data-theme="tactical"]{
  --bg:#0d0f12; --panel:#15181d; --panel2:#1b1f26; --border:#282d36; --border-hi:#4a525f;
  --text:#f4f5f7; --muted:#98a0ad; --accent:#ff4655; --accent2:#ffffff;
  --win:#38d9a9; --win-wash:rgba(56,217,169,.13); --lose:#5c636f; --glow:0 0 0 1px rgba(255,70,85,.45), 0 0 18px rgba(255,70,85,.18);
  --accent-rgb:255,70,85; --accent2-rgb:255,255,255; --node:#181b21; --grid:rgba(255,70,85,.06);
}
#ls-app[data-theme="matrix"]{
  --bg:#000000; --panel:#070d07; --panel2:#0a140a; --border:#123512; --border-hi:#1f6b1f;
  --text:#c9ffc9; --muted:#5f9c5f; --accent:#00ff66; --accent2:#9dff00;
  --win:#00ff66; --win-wash:rgba(0,255,102,.14); --lose:#2f5d2f; --glow:0 0 0 1px rgba(0,255,102,.45), 0 0 18px rgba(0,255,102,.2);
  --accent-rgb:0,255,102; --accent2-rgb:157,255,0; --node:#060f06; --grid:rgba(0,255,102,.06);
}
#ls-app[data-theme="retro"]{
  --bg:#17092b; --panel:#221040; --panel2:#2a1450; --border:#3d1f6e; --border-hi:#6b35b8;
  --text:#ffe9fb; --muted:#b78ccf; --accent:#ff3fa4; --accent2:#ffd803;
  --win:#ffd803; --win-wash:rgba(255,216,3,.14); --lose:#6c4b90; --glow:0 0 0 1px rgba(255,63,164,.45), 0 0 20px rgba(255,63,164,.22);
  --accent-rgb:255,63,164; --accent2-rgb:255,216,3; --node:#26124a; --grid:rgba(255,63,164,.07);
}
#ls-app[data-theme="light"]{
  --bg:#f4f6fa; --panel:#ffffff; --panel2:#ffffff; --border:#dfe4ee; --border-hi:#b6bfd2;
  --text:#141824; --muted:#5b6377; --accent:#2563eb; --accent2:#0ea5e9;
  --win:#16a34a; --win-wash:rgba(22,163,74,.12); --lose:#9aa3b4; --glow:0 0 0 1px rgba(37,99,235,.3), 0 0 12px rgba(37,99,235,.12);
  --accent-rgb:37,99,235; --accent2-rgb:14,165,233; --node:#ffffff; --grid:rgba(37,99,235,.05);
}

/* ---------- Display face ----------
   Self-hosted so the tool looks right even embedded outside leaguespawn.com
   (e.g. the iframe embed on a third-party blog, which won't have the theme's
   copy). Same face as the site theme for brand consistency, applied ONLY to
   the brand lockup, champion name, and section labels — never to team names,
   scores, or body UI, which stay on the fast system stack. */
@font-face{
  font-family:'Rajdhani'; font-style:normal; font-weight:700; font-display:swap;
  src:url('../fonts/rajdhani-700.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+20AC,U+2122;
}
[dir="rtl"] #ls-app{ --ls-display:inherit; }   /* Rajdhani has no Arabic glyphs */

/* ---------- App shell ---------- */
#ls-app{
  --ls-display:'Rajdhani', var(--wp--preset--font-family--display, inherit);
  --r:10px;
  background:var(--bg); color:var(--text); border:1px solid var(--border);
  border-radius:14px; overflow:hidden; margin:28px 0;
  font-family:'Inter',ui-sans-serif,system-ui,'Segoe UI',sans-serif;
  font-size:14px; line-height:1.45; box-sizing:border-box;
  contain:layout style;
}
#ls-app *,#ls-app *::before,#ls-app *::after{box-sizing:border-box;}
#ls-app button{font-family:inherit;}

.ls-bar{
  position:relative;   /* anchors the Customize flyout panel */
  display:flex; align-items:center; gap:14px; flex-wrap:wrap;
  padding:12px 16px; background:var(--panel);
  border-bottom:1px solid var(--border);
}
.ls-customize-btn{ flex:none; white-space:nowrap; }
.ls-customize-btn[aria-expanded="true"]{ border-color:var(--accent); color:var(--text); }
.ls-customize-panel{
  position:absolute; top:calc(100% + 8px); right:16px; z-index:50;
  display:flex; flex-wrap:wrap; gap:10px; width:min(520px, calc(100% - 32px));
  background:var(--panel); border:1px solid var(--border-hi); border-radius:12px;
  padding:14px; box-shadow:0 16px 40px rgba(0,0,0,.5);
}
/* `display:flex` above is a class rule and therefore BEATS the browser's
   built-in `[hidden]{display:none}` (a UA rule with no specificity). Without
   this the flyout was permanently visible and the Customize button appeared
   to do nothing — it toggled the attribute correctly, but nothing changed on
   screen. Any component that sets `display` must restate [hidden]. */
.ls-customize-panel[hidden]{ display:none !important; }
.ls-customize-panel .ls-field{ flex:1 1 220px; }
@media (max-width:700px){
  .ls-customize-panel{ left:16px; right:16px; width:auto; }
}
.ls-brand{display:flex; align-items:center; gap:9px; min-width:0;}
.ls-logo{
  display:grid; place-items:center; width:32px; height:32px; border-radius:9px; flex:none;
  background:linear-gradient(135deg,var(--accent2),var(--accent)); color:#fff; font-size:15px;
}
.ls-brand-name{font-family:var(--ls-display); font-weight:700; letter-spacing:.4px; line-height:1.1; font-size:14px;}
.ls-brand-sub{font-size:10px; text-transform:uppercase; letter-spacing:1.4px; color:var(--muted);}
.ls-title-input{flex:1 1 200px; min-width:150px; font-weight:600;}
.ls-bar-right{display:flex; gap:10px; margin-left:auto; flex-wrap:wrap;}

.ls-body{display:flex; align-items:stretch; flex-wrap:wrap;}
.ls-rail{
  width:288px; flex:none; padding:14px; display:flex; flex-direction:column; gap:14px;
  background:var(--bg); border-right:1px solid var(--border);
}
@media (max-width:860px){
  .ls-rail{width:100%; border-right:none; border-bottom:1px solid var(--border);}
}

.ls-panel{background:var(--panel); border:1px solid var(--border); border-radius:12px; padding:13px;}
.ls-panel-head{display:flex; align-items:center; justify-content:space-between; margin-bottom:9px;}
.ls-h{
  font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:1.4px;
  color:var(--muted); margin:0 0 9px;
}
.ls-panel-head .ls-h{margin:0;}
.ls-pill{
  font-size:11px; font-weight:700; padding:2px 9px; border-radius:99px;
  background:var(--panel2); border:1px solid var(--border); color:var(--accent);
}

/* ---------- Controls ---------- */
.ls-input{
  width:100%; background:var(--panel2); color:var(--text);
  border:1px solid var(--border); border-radius:var(--r);
  padding:8px 10px; font-size:13px; outline:none; transition:border-color .12s, box-shadow .12s;
}
.ls-input:focus{border-color:var(--accent); box-shadow:var(--glow);}
.ls-textarea{min-height:168px; resize:vertical; font-family:ui-monospace,'Cascadia Code',Consolas,monospace; line-height:1.65;}
.ls-field{display:flex; flex-direction:column; gap:4px;}
.ls-compact{width:150px;}
.ls-label{font-size:10px; text-transform:uppercase; letter-spacing:1.2px; color:var(--muted);}
.ls-row{display:flex; gap:8px; margin-top:9px;}
.ls-row > *{flex:1;}
.ls-stack{display:flex; flex-direction:column; gap:8px;}
.ls-hint{font-size:11.5px; color:var(--muted); margin:10px 0 0;}

.ls-btn{
  background:var(--panel2); color:var(--text); border:1px solid var(--border);
  border-radius:var(--r); padding:9px 12px; font-size:12.5px; font-weight:600;
  cursor:pointer; transition:border-color .12s, transform .06s, box-shadow .12s;
  text-align:center; white-space:nowrap;
}
.ls-btn:hover{border-color:var(--border-hi);}
.ls-btn:active{transform:translateY(1px);}
.ls-btn-primary{
  background:linear-gradient(135deg,var(--accent2),var(--accent)); color:#fff; border-color:transparent;
}
#ls-app[data-theme="matrix"] .ls-btn-primary,
#ls-app[data-theme="retro"] .ls-btn-primary{color:#08120a;}
.ls-btn-primary:hover{box-shadow:var(--glow);}
.ls-btn-ghost{background:transparent; color:var(--muted); font-weight:500;}
.ls-btn-xs{padding:3px 8px; font-size:10.5px; flex:none;}

/* ---------- Roster / logo uploader ---------- */
.ls-roster{display:flex; flex-direction:column; gap:5px; max-height:230px; overflow-y:auto;}
.ls-rrow{display:flex; align-items:center; gap:8px;}
.ls-rname{
  flex:1; min-width:0; font-size:12px; font-weight:600;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.ls-rdrop{
  width:28px; height:28px; flex:none; border-radius:7px; cursor:pointer; padding:0;
  border:1px dashed var(--border); background:var(--panel2); overflow:hidden;
  display:grid; place-items:center;
}
.ls-rdrop:hover{border-color:var(--accent);}
.ls-rdrop img{width:100%; height:100%; object-fit:cover; display:block;}
.ls-rclear{
  background:transparent; border:none; color:var(--muted); cursor:pointer;
  font-size:12px; padding:2px 5px; border-radius:5px; flex:none;
}
.ls-rclear:hover{color:var(--text); background:var(--panel2);}

/* Avatar shown inside a bracket slot */
.ls-av{
  width:19px; height:19px; flex:none; border-radius:5px; object-fit:cover; display:block;
}
/* Placeholder keeps TBD/BYE rows exactly as tall as rows with a real avatar.
   Without it, row height varies by round and every connector into an unplayed
   match is drawn off-centre. */
.ls-av-ph{
  background:transparent; border:1px dashed var(--border); box-sizing:border-box;
}
.ls-av-i{
  display:grid; place-items:center; font-size:8.5px; font-weight:800;
  color:#fff; letter-spacing:.2px; text-transform:uppercase;
}
.ls-btn-discord{background:#5865f2; color:#fff; border-color:transparent;}
.ls-btn-discord:hover{box-shadow:0 0 16px rgba(88,101,242,.4);}

/* ---------- Stage ---------- */
.ls-stage-wrap{flex:1 1 460px; min-width:0; display:flex; flex-direction:column;}
.ls-stage-tools{
  display:flex; align-items:center; gap:7px; padding:9px 14px;
  border-bottom:1px solid var(--border); background:var(--panel);
}
.ls-zbtn{
  width:30px; height:30px; border-radius:8px; background:var(--panel2); color:var(--text);
  border:1px solid var(--border); cursor:pointer; font-size:15px; line-height:1; font-weight:600;
}
.ls-zbtn:hover{border-color:var(--accent);}
.ls-zbtn-wide{width:auto; padding:0 12px; font-size:12px;}
.ls-zoomval{font-size:11.5px; color:var(--muted); width:46px; text-align:center; font-variant-numeric:tabular-nums;}
.ls-tip{font-size:11px; color:var(--muted); margin-left:auto;}
@media (max-width:700px){.ls-tip{display:none;}}

.ls-viewport{
  position:relative; height:600px; min-height:600px;   /* reserved → zero CLS */
  overflow:hidden; cursor:grab; touch-action:none; outline:none;
  background:
    radial-gradient(circle at 1px 1px, var(--grid) 1px, transparent 0) 0 0/26px 26px,
    var(--bg);
}
/* The 600px reserved height (also set inline by the shortcode's `height`
   attribute, hence !important here) is sized for desktop. On a ~375px phone
   it eats most of the viewport before the settings panel above it even ends.
   Shrinking it doesn't fix the panel-before-canvas ordering — that's a
   deliberate layout call, not a polish-scope fix — but it stops the canvas
   itself from being disproportionately tall relative to the screen. */
@media (max-width:600px){
  .ls-viewport{ height:420px !important; min-height:420px !important; }
}
.ls-viewport.ls-grabbing{cursor:grabbing;}
.ls-viewport:focus-visible{box-shadow:inset var(--glow);}
.ls-pan{position:absolute; top:0; left:0; transform-origin:0 0; will-change:transform;}
.ls-stage{position:relative; padding:74px 40px 52px;}   /* top room for title + round labels */
/* Origin is set from JS to match #ls-nodes. It must NOT use inset:0 —
   that anchors the SVG to .ls-stage's border box, while the nodes live
   inside its 74px/40px padding, so every connector was drawn offset by
   exactly that padding. */
.ls-layer{position:relative;}
.ls-links{position:absolute; left:0; top:0; pointer-events:none; overflow:visible;}
.ls-nodes{position:relative;}
.ls-stage-title{
  position:absolute; top:16px; left:40px; display:flex; align-items:center; gap:11px;
  max-width:calc(100% - 60px);   /* never spill past the canvas edge either */
}
.ls-stage-logo{height:38px; max-width:150px; object-fit:contain; display:block; flex:none;}
/* User-entered tournament names are unbounded length, but this sits in a
   fixed 74px band reserved above round-1 cards (see .ls-stage padding) —
   without a width cap and nowrap, a long title wraps to a second line and
   spills straight into the first match card below it. Truncate instead. */
.ls-stage-names{display:flex; flex-direction:column; gap:2px; min-width:0; max-width:280px;}
.ls-stage-h1{
  font-family:var(--ls-display); font-size:19px; font-weight:800; letter-spacing:.3px; color:var(--text);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.ls-stage-game{
  font-size:10px; font-weight:700; letter-spacing:1.5px; text-transform:uppercase;
  color:var(--accent); white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.ls-watermark{
  position:absolute; right:22px; bottom:16px; font-size:11px; font-weight:600;
  letter-spacing:1.1px; color:var(--muted); opacity:.62; pointer-events:none;
  text-transform:uppercase;
}
.ls-empty{
  position:absolute; inset:0; display:grid; place-content:center; justify-items:center;
  gap:10px; color:var(--muted); pointer-events:none; text-align:center; padding:20px;
}
.ls-empty[hidden]{display:none;}   /* [hidden] must beat the display:grid above */
.ls-empty-mark{font-size:34px; color:var(--accent); opacity:.5;}
.ls-empty p{margin:0;}

/* ---------- Match nodes ---------- */
.ls-match{
  position:absolute; width:242px; background:var(--node);
  border:1px solid var(--border); border-radius:9px; overflow:hidden;
}
.ls-match.ls-live{border-color:var(--border-hi);}
.ls-mhead{
  display:flex; justify-content:space-between; padding:2px 8px;
  font-size:9.5px; letter-spacing:1px; text-transform:uppercase;
  color:var(--muted); background:var(--panel); border-bottom:1px solid var(--border);
}
.ls-slot{
  display:flex; align-items:center; gap:7px; padding:7px 8px; cursor:pointer;
  transition:background .1s;
}
.ls-slot + .ls-slot{border-top:1px solid var(--border);}
.ls-slot:hover:not(.ls-empty-slot){background:var(--panel2);}
.ls-seed{
  font-size:9.5px; color:var(--muted); width:17px; flex:none;
  font-variant-numeric:tabular-nums; text-align:right;
}
.ls-name{
  flex:1; min-width:0; font-size:12.5px; font-weight:600;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
/* NOTE: --win-wash is an explicit rgba per theme, NOT color-mix(). html2canvas
   1.4.1 throws "unsupported color function" on color-mix and aborts the whole
   PNG export, so every colour used here must stay in a format it can parse. */
.ls-slot.ls-won{background:linear-gradient(90deg,var(--win-wash),transparent);}
.ls-slot.ls-won .ls-name{color:var(--win);}
.ls-slot.ls-won .ls-seed{color:var(--win);}
.ls-slot.ls-out .ls-name{color:var(--lose); text-decoration:line-through; text-decoration-thickness:1px;}
.ls-empty-slot{cursor:default;}
.ls-empty-slot .ls-name{color:var(--lose); font-style:italic; font-weight:500;}
.ls-score{
  width:34px; flex:none; text-align:center; background:transparent; color:var(--muted);
  border:1px solid transparent; border-radius:6px; font-size:12px; font-weight:700;
  padding:1px 2px; font-variant-numeric:tabular-nums; -moz-appearance:textfield;
}
.ls-score::-webkit-outer-spin-button,.ls-score::-webkit-inner-spin-button{-webkit-appearance:none; margin:0;}
.ls-score:hover{border-color:var(--border);}
.ls-score:focus{border-color:var(--accent); color:var(--text); outline:none;}
.ls-won .ls-score{color:var(--win);}

.ls-champ{
  position:absolute; width:242px; padding:12px; border-radius:11px; text-align:center;
  background:linear-gradient(135deg,var(--accent2),var(--accent));
  box-shadow:var(--glow);
}
#ls-app[data-theme="matrix"] .ls-champ,
#ls-app[data-theme="retro"] .ls-champ{color:#0a140a;}
#ls-app[data-theme="cyber"] .ls-champ,
#ls-app[data-theme="tactical"] .ls-champ,
#ls-app[data-theme="light"] .ls-champ{color:#fff;}
.ls-champ-lbl{font-size:9.5px; letter-spacing:2px; text-transform:uppercase; opacity:.85;}
.ls-champ-name{font-size:16px; font-weight:800; margin-top:3px; word-break:break-word;}

.ls-rlabel{
  position:absolute; font-size:10px; font-weight:700; letter-spacing:1.6px;
  text-transform:uppercase; color:var(--muted); white-space:nowrap;
}
.ls-blabel{
  position:absolute; font-size:11px; font-weight:800; letter-spacing:2px;
  text-transform:uppercase; color:var(--accent); white-space:nowrap; opacity:.8;
}
/* ---------- Connectors, flow animation, path tracing ---------- */
.ls-link{fill:none; stroke:var(--border-hi); stroke-width:1.6; opacity:.7; transition:opacity .18s, stroke .18s;}
.ls-link.ls-link-hot{stroke:var(--accent); opacity:.95; stroke-width:2;}

/* A second path layered over each "hot" connector; the dash marches along the
   line in the direction of travel, reading as the team advancing. */
.ls-flow{
  fill:none; stroke:var(--accent); stroke-width:2.4; stroke-linecap:round;
  stroke-dasharray:10 190; opacity:0;
}
#ls-app.ls-anim .ls-flow{opacity:.95; animation:ls-march 2.6s linear infinite;}
@keyframes ls-march{ from{stroke-dashoffset:200;} to{stroke-dashoffset:0;} }

/* Trace: dim everything that isn't on the hovered team's path. */
#ls-app.ls-tracing .ls-match{transition:opacity .16s, filter .16s;}
#ls-app.ls-tracing .ls-match.ls-dim{opacity:.28; filter:saturate(.4);}
#ls-app.ls-tracing .ls-link.ls-dim{opacity:.1;}
#ls-app.ls-tracing .ls-flow.ls-dim{opacity:0; animation:none;}
.ls-match.ls-trace{box-shadow:var(--glow); border-color:var(--accent);}
.ls-link.ls-trace{stroke:var(--accent); opacity:1; stroke-width:2.4;}

/* Respect users who've asked the OS for less motion. */
@media (prefers-reduced-motion:reduce){
  #ls-app.ls-anim .ls-flow{animation:none; opacity:.6;}
}

/* ---------- Tournament logo + toggles ---------- */
.ls-tlogo{
  width:38px; height:38px; flex:none; border-radius:10px; cursor:pointer; padding:0;
  border:1px dashed var(--border); background:var(--panel2); overflow:hidden;
  display:grid; place-items:center;
}
.ls-tlogo:hover{border-color:var(--accent);}
.ls-tlogo img{width:100%; height:100%; object-fit:contain; display:block;}
.ls-tlogo-ph{color:var(--muted); font-size:17px; line-height:1;}

.ls-toggle{
  display:flex; align-items:center; gap:5px; font-size:11px; color:var(--muted);
  cursor:pointer; user-select:none; padding:3px 8px; border-radius:7px;
  border:1px solid var(--border); background:var(--panel2); white-space:nowrap;
}
.ls-toggle:hover{border-color:var(--border-hi); color:var(--text);}
.ls-toggle input{accent-color:var(--accent); margin:0; cursor:pointer;}
@media (max-width:820px){.ls-toggle span{display:none;}}

/* ---------- Full-width leaderboard ---------- */
.ls-board{
  border-top:1px solid var(--border); background:var(--panel); padding:16px 18px 18px;
}
.ls-board[hidden]{display:none;}
.ls-board-head{display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:12px;}
.ls-board-title{
  display:flex; align-items:center; gap:9px; margin:0; font-family:var(--ls-display); font-size:16px; font-weight:800;
  letter-spacing:1.4px; text-transform:uppercase; color:var(--text);
}
.ls-board-ico{font-size:16px;}
.ls-pill-lg{padding:5px 13px; font-size:11.5px;}
.ls-tablescroll{overflow-x:auto; -webkit-overflow-scrolling:touch;}

/* ---------- Standings table ---------- */
.ls-st{width:100%; border-collapse:collapse; font-size:12.5px; min-width:560px;}
.ls-st th{
  text-align:left; font-size:10px; text-transform:uppercase; letter-spacing:1.1px;
  color:var(--muted); padding:9px 10px; border-bottom:1px solid var(--border); font-weight:700;
  white-space:nowrap;
}
.ls-st th.ls-c-w{color:var(--win);}
.ls-st th.ls-c-l{color:var(--lose);}
.ls-st th.ls-c-a{color:var(--accent);}
.ls-st th.ls-c-b{color:var(--accent2);}
.ls-st td{padding:9px 10px; border-bottom:1px solid var(--border); white-space:nowrap;}
.ls-st tbody tr:last-child td{border-bottom:none;}
.ls-st tbody tr:hover{background:var(--panel2);}
.ls-st .ls-st-rank{color:var(--muted); width:52px; font-variant-numeric:tabular-nums; font-weight:700;}
.ls-st-teamcell{display:flex; align-items:center; gap:9px;}
.ls-st .ls-st-name{font-weight:700; color:var(--text);}
.ls-st .ls-st-num{text-align:right; font-variant-numeric:tabular-nums; font-weight:700;}
.ls-st .ls-st-rec{font-family:ui-monospace,Consolas,monospace; color:var(--muted); font-weight:600;}
.ls-st .ls-v-w{color:var(--win);}
.ls-st .ls-v-l{color:var(--lose);}
.ls-st .ls-v-b{color:var(--accent2);}
.ls-st tr.ls-st-qual{background:linear-gradient(90deg,var(--win-wash),transparent);}
.ls-st tr.ls-st-qual .ls-st-rank{color:var(--win);}
.ls-st-badge{color:var(--win); font-size:11px;}

/* ---------- Modal ---------- */
.ls-modal{
  position:fixed; inset:0; z-index:99999; background:rgba(4,6,12,.78);
  display:grid; place-items:center; padding:18px; backdrop-filter:blur(3px);
}
.ls-modal[hidden]{display:none;}
.ls-modal-card{
  background:var(--panel); border:1px solid var(--border-hi); border-radius:14px;
  width:min(560px,100%); max-height:86vh; overflow:auto; color:var(--text);
}
.ls-modal-head{
  display:flex; align-items:center; justify-content:space-between; gap:10px;
  padding:14px 16px; border-bottom:1px solid var(--border); position:sticky; top:0; background:var(--panel);
}
.ls-modal-head .ls-h{margin:0;}
.ls-modal-body{padding:16px; display:flex; flex-direction:column; gap:11px;}
.ls-modal-body p{margin:0; font-size:12.5px; color:var(--muted);}
.ls-mdl-label{
  font-size:10px; font-weight:700; text-transform:uppercase; letter-spacing:1.1px;
  color:var(--muted); margin-bottom:-4px;
}
.ls-mdl-notes{resize:vertical; min-height:64px; line-height:1.5;}
.ls-x{
  background:transparent; border:none; color:var(--muted); font-size:15px;
  cursor:pointer; padding:4px 7px; border-radius:7px;
}
.ls-x:hover{color:var(--text); background:var(--panel2);}
.ls-code{
  background:var(--bg); border:1px solid var(--border); border-radius:9px; padding:11px;
  font-family:ui-monospace,Consolas,monospace; font-size:11px; line-height:1.55;
  white-space:pre-wrap; word-break:break-all; color:var(--text); max-height:200px; overflow:auto;
}
.ls-toast{
  position:fixed; left:50%; bottom:26px; transform:translateX(-50%) translateY(8px);
  background:var(--panel); color:var(--text); border:1px solid var(--accent);
  border-radius:10px; padding:10px 18px; font-size:13px; font-weight:600; z-index:100000;
  box-shadow:var(--glow); opacity:0; transition:opacity .18s, transform .18s;
  font-family:'Inter',system-ui,sans-serif; pointer-events:none;
}
.ls-toast.ls-show{opacity:1; transform:translateX(-50%) translateY(0);}

/* ---------- SEO prose (inherits WP theme, lightly tuned) ---------- */
.ls-seo{max-width:860px; margin:0 auto 26px;}
.ls-seo .ls-lede{font-size:1.05em; line-height:1.7;}
.ls-seo .ls-kw{font-size:.9em; opacity:.75;}
.ls-seo .ls-steps > li{margin-bottom:14px;}
.ls-tablewrap{overflow-x:auto; -webkit-overflow-scrolling:touch;}
.ls-table{width:100%; border-collapse:collapse; font-size:.92em; min-width:600px;}
.ls-table th,.ls-table td{border:1px solid rgba(128,128,128,.28); padding:9px 11px; text-align:left; vertical-align:top;}
.ls-table thead th{background:rgba(128,128,128,.09);}

@media print{ .ls-rail,.ls-stage-tools,.ls-bar-right{display:none!important;} }

/* ═══════════════════════════════════════════════════════════════════════
   HARDENING + VISUAL REDESIGN  (v1.1)

   Everything below is prefixed with #ls-app so it wins on specificity against
   whatever theme the shortcode lands in. A theme class collision (.ls-slot)
   once inflated every match row to 280px and shrank the bracket to 20% zoom;
   id-scoping makes that class of failure impossible rather than unlikely.
   ═══════════════════════════════════════════════════════════════════════ */

/* ---- Reset foreign inheritance ----
   `:where(#ls-app)` deliberately carries ZERO specificity (unlike a bare
   `#ls-app` prefix, which is an ID and outranks every plain `.classname`
   rule in this file regardless of source order). Without :where(), this
   defensive baseline silently zeroed padding on .ls-stage, .ls-panel,
   .ls-modal-card and others that declare their own padding via a plain
   class selector — the stage's lost padding is what let the tournament
   title overlap round-1 match cards. The reset still applies to any
   genuinely untouched element; it just can no longer beat an intentional
   component rule. */
#ls-app, #ls-app * { min-height: 0; min-width: 0; }
:where(#ls-app) :is(div, span, p, h2, h3, ul, ol, li, figure, table) { margin: 0; padding: 0; }
#ls-app :is(img, svg) { max-width: none; }
#ls-app { line-height: 1.4; letter-spacing: normal; text-transform: none; }

/* ---- Match card: glass + neon glow ----
   NOTE: card width (264px) and .ls-slot height (40px, below) are load-bearing
   for the connector math in bracket.js — drawLinks() measures real slot
   centres, but PITCH is a fixed layout constant sized to the CURRENT row
   height. Changing slot height requires re-verifying connector alignment
   across every format/size, not just eyeballing it.

   Per-card backdrop-filter is deliberately NOT used here: a 64-team bracket
   renders 127 of these, and stacking that many independent blurs is a real
   scroll/zoom jank risk on modest hardware. The "glass" read comes instead
   from a cheap layered gradient + inner highlight + outer glow, which costs
   nothing at paint time. Real backdrop-filter is reserved for the handful of
   larger chrome elements below (panels, toolbar, modal) where it's cheap. */
#ls-app .ls-match{
  /* position MUST stay absolute — bracket.js places every match card via
     inline top/left computed from the layout tree. This selector's higher
     specificity (#ls-app + class) previously overrode the base .ls-match
     rule's position:absolute with :relative, which doesn't remove the
     top/left offset but reinterprets it as an offset from the card's normal
     document-flow position instead of from #ls-nodes's origin — every card
     still "moved", just not to the coordinate the connector math assumed,
     producing exactly the disconnected-lines bug this comment now guards
     against. Never redeclare position on this selector without absolute. */
  position:absolute;
  width:264px; border-radius:11px; overflow:hidden;
  background:
    linear-gradient(180deg, rgba(255,255,255,.07), rgba(255,255,255,.015) 40%),
    linear-gradient(180deg, var(--panel2), var(--node));
  border:1px solid rgba(255,255,255,.10);
  box-shadow:
    0 0 0 1px rgba(var(--accent-rgb),.20),
    0 0 20px rgba(var(--accent-rgb),.09),
    inset 0 1px 0 rgba(255,255,255,.07),
    0 6px 18px rgba(0,0,0,.4);
  transition: box-shadow .2s var(--ls-ease, ease), border-color .2s var(--ls-ease, ease);
}
#ls-app .ls-match.ls-live{
  border-color:rgba(var(--accent-rgb),.55);
  box-shadow:
    0 0 0 1px rgba(var(--accent-rgb),.5),
    0 0 28px rgba(var(--accent-rgb),.3),
    inset 0 1px 0 rgba(255,255,255,.08),
    0 6px 20px rgba(0,0,0,.45);
}
/* Pulsing glow on live matches, gated by the existing Animate toggle so it
   respects the same on/off switch as the connector flow animation. */
#ls-app.ls-anim .ls-match.ls-live{ animation: ls-glow-pulse 2.6s ease-in-out infinite; }
@keyframes ls-glow-pulse{
  0%,100%{ box-shadow:0 0 0 1px rgba(var(--accent-rgb),.45), 0 0 22px rgba(var(--accent-rgb),.22), inset 0 1px 0 rgba(255,255,255,.08), 0 6px 20px rgba(0,0,0,.45); }
  50%    { box-shadow:0 0 0 1px rgba(var(--accent-rgb),.7),  0 0 36px rgba(var(--accent-rgb),.42), inset 0 1px 0 rgba(255,255,255,.08), 0 6px 20px rgba(0,0,0,.45); }
}
@media (prefers-reduced-motion:reduce){ #ls-app.ls-anim .ls-match.ls-live{ animation:none; } }

/* ---- Card style variants ----
   Only visual treatment (background/border/shadow) differs between variants.
   "compact" is the one exception — it also shrinks .ls-slot height, which is
   safe only because the connector system measures real rendered geometry
   (nodeGeo() in bracket.js) rather than assuming a fixed row height. */
#ls-app[data-cardstyle="modern"] .ls-match{
  background:var(--panel2);
  border:1px solid var(--border);
  box-shadow:0 1px 2px rgba(0,0,0,.3), 0 8px 20px rgba(0,0,0,.18);
  border-radius:14px;
}
#ls-app[data-cardstyle="modern"] .ls-match.ls-live{
  border-color:var(--accent); box-shadow:0 0 0 1px var(--accent), 0 8px 20px rgba(0,0,0,.22);
}
#ls-app[data-cardstyle="modern"].ls-anim .ls-match.ls-live{ animation:none; }
#ls-app[data-cardstyle="modern"] .ls-mhead{ background:transparent; border-bottom-color:var(--border); }

#ls-app[data-cardstyle="solid"] .ls-match{
  background:var(--node);
  border:2px solid var(--border-hi);
  box-shadow:0 4px 10px rgba(0,0,0,.35);
}
#ls-app[data-cardstyle="solid"] .ls-match.ls-live{
  border-color:var(--accent); box-shadow:0 4px 10px rgba(0,0,0,.4);
}
#ls-app[data-cardstyle="solid"].ls-anim .ls-match.ls-live{ animation:none; }
#ls-app[data-cardstyle="solid"] .ls-mhead{ background:var(--panel); }

/* NOTE: card WIDTH must stay at the base 264px — bracket.js positions every
   column using a fixed MW=264 constant for connector X coordinates. Narrowing
   the card here without also touching MW would leave connectors landing in
   empty space past the card's new edge. Only row height is safe to compact,
   since that's measured live per-card by nodeGeo(), not assumed. */
#ls-app[data-cardstyle="compact"] .ls-mhead{ padding:2px 6px; font-size:8px; }
#ls-app[data-cardstyle="compact"] .ls-slot{ height:30px; gap:6px; padding:0 7px; }
#ls-app[data-cardstyle="compact"] .ls-av{ width:16px; height:16px; }
#ls-app[data-cardstyle="compact"] .ls-name{ font-size:10.5px; }
#ls-app[data-cardstyle="compact"] .ls-score{ width:24px; height:19px; font-size:10.5px; }

#ls-app .ls-mhead{
  display:flex; justify-content:space-between; align-items:center; gap:6px;
  padding:3px 7px 3px 9px; font-size:9px; font-weight:700; letter-spacing:.12em;
  line-height:1.6; color:var(--muted); text-transform:uppercase;
  background:rgba(0,0,0,.28); border-bottom:1px solid rgba(255,255,255,.08);
}
#ls-app .ls-mhead-status{ display:flex; align-items:center; gap:4px; }

/* ---- Match detail buttons (notes / stream link) ----
   Deliberately outside .ls-slot so they never interfere with the winner-pick
   click handler, and excluded from the pan/drag pointerdown handler in JS. */
#ls-app .ls-mbtn{
  display:grid; place-items:center; width:15px; height:15px; flex:none;
  border-radius:4px; border:none; background:transparent; color:var(--muted);
  font-size:9px; line-height:1; cursor:pointer; padding:0; text-decoration:none;
  transition: color .12s, background .12s, box-shadow .12s;
}
#ls-app .ls-mbtn:hover{ color:var(--text); background:rgba(255,255,255,.1); }
#ls-app .ls-mbtn-has{
  color:var(--accent);
  box-shadow:0 0 0 1px rgba(var(--accent-rgb),.4), 0 0 8px rgba(var(--accent-rgb),.35);
}
#ls-app .ls-mbtn-watch{ color:var(--win); }
#ls-app .ls-mbtn-watch:hover{ color:var(--win); box-shadow:0 0 8px rgba(var(--accent-rgb),.3); }

/* ---- Team row ---- */
#ls-app .ls-slot{
  display:flex; align-items:center; gap:9px;
  height:40px; padding:0 9px; cursor:pointer; position:relative;
  line-height:1.2; transition:background .12s;
}
#ls-app .ls-slot + .ls-slot{ border-top:1px solid var(--border); }
#ls-app .ls-slot:hover:not(.ls-empty-slot){ background:rgba(255,255,255,.05); }

#ls-app .ls-seed{
  width:14px; flex:none; text-align:center; font-size:9.5px; font-weight:700;
  color:var(--lose); font-variant-numeric:tabular-nums; line-height:1;
}
#ls-app .ls-av{ width:22px; height:22px; border-radius:5px; flex:none; }
#ls-app .ls-av-i{ font-size:9px; letter-spacing:0; }

#ls-app .ls-name{
  flex:1; min-width:0; font-size:12px; font-weight:700; line-height:1.2;
  letter-spacing:.03em; text-transform:uppercase;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}

/* Score as a chip, like broadcast graphics — reads instantly at a glance. */
#ls-app .ls-score{
  width:30px; height:24px; flex:none; text-align:center;
  background:rgba(0,0,0,.35); color:var(--muted);
  border:1px solid var(--border); border-radius:6px;
  font-size:12.5px; font-weight:800; line-height:1; padding:0;
  font-variant-numeric:tabular-nums;
}
#ls-app .ls-score:focus{ border-color:var(--accent); color:var(--text); background:rgba(0,0,0,.5); }
#ls-app span.ls-score{ background:transparent; border-color:transparent; }

/* ---- Winner / loser states ---- */
#ls-app .ls-slot.ls-won{
  background:linear-gradient(90deg, var(--win-wash), transparent 70%);
  box-shadow:inset 3px 0 0 var(--win);
}
#ls-app .ls-slot.ls-won .ls-name{ color:var(--win); }
#ls-app .ls-slot.ls-won .ls-seed{ color:var(--win); }
#ls-app .ls-slot.ls-won .ls-score{
  background:var(--win); color:#06210f; border-color:var(--win);
}
#ls-app .ls-slot.ls-out .ls-name{ color:var(--lose); text-decoration:none; opacity:.72; }
#ls-app .ls-slot.ls-out .ls-av{ opacity:.45; filter:grayscale(.7); }
#ls-app .ls-empty-slot .ls-name{
  color:var(--lose); font-style:normal; font-weight:600; opacity:.55; text-transform:uppercase;
}

/* ---- Labels ---- */
#ls-app .ls-rlabel{
  font-size:10px; font-weight:800; letter-spacing:.18em; color:var(--muted);
  text-transform:uppercase; line-height:1;
}
#ls-app .ls-blabel{
  font-family:var(--ls-display); font-size:13px; font-weight:700; letter-spacing:.2em; color:var(--accent);
  text-transform:uppercase; line-height:1; opacity:1;
  padding-bottom:6px; border-bottom:2px solid var(--accent);
}

/* ---- Connectors: brighter, broadcast-style ---- */
#ls-app .ls-link{ stroke:rgba(255,255,255,.28); stroke-width:2; opacity:1; }
#ls-app .ls-link.ls-link-hot{ stroke:var(--accent); opacity:.9; }
#ls-app .ls-flow{ stroke:var(--accent); stroke-width:2.6; }

/* ---- Connector line styles ----
   Path SHAPE (elbow vs S-curve) is generated in JS — connectorPath() in
   bracket.js — because SVG path geometry can't be expressed in CSS. Only the
   stroke treatment (weight, dash, glow) lives here. Endpoints never move
   between styles, only the middle geometry, so this never risks the
   connector-alignment correctness that's been hardened elsewhere. */
#ls-app[data-linestyle="neon"] .ls-link{
  stroke-width:2.6; opacity:.85; filter:url(#ls-neon-glow);
}
#ls-app[data-linestyle="neon"] .ls-link.ls-link-hot{
  stroke-width:3; opacity:1; filter:url(#ls-neon-glow);
}
#ls-app[data-linestyle="neon"] .ls-flow{ stroke-width:3.2; filter:url(#ls-neon-glow); }

#ls-app[data-linestyle="minimal"] .ls-link{
  stroke-width:1.2; stroke-dasharray:5 4; opacity:.55;
}
#ls-app[data-linestyle="minimal"] .ls-link.ls-link-hot{
  stroke-width:1.4; stroke-dasharray:5 4; opacity:.85;
}
#ls-app[data-linestyle="minimal"] .ls-flow{ stroke-width:1.6; }

#ls-app[data-linestyle="curved"] .ls-link{ stroke-width:2.2; }
#ls-app[data-linestyle="curved"] .ls-link.ls-link-hot{ stroke-width:2.6; }

/* ---- Champion: the biggest glow in the app, it's the payoff moment ---- */
#ls-app .ls-champ{
  width:264px; padding:16px 14px; border-radius:12px;
  box-shadow:
    0 0 0 1px rgba(var(--accent-rgb),.6),
    0 0 40px rgba(var(--accent-rgb),.4),
    0 0 90px rgba(var(--accent2-rgb),.25),
    0 8px 28px rgba(0,0,0,.5);
}
#ls-app .ls-champ-lbl{ font-size:9px; letter-spacing:.24em; font-weight:800; }
#ls-app .ls-champ-name{ font-family:var(--ls-display); font-size:19px; font-weight:700; letter-spacing:.02em; }

/* ═══════════════════════════════════════════════════════════════════════
   GLASS CHROME  (v1.2) — real backdrop-filter on the handful of larger UI
   surfaces (toolbar, side panels, modal). Cheap here because there are only
   ever a few of these on screen at once, unlike match cards (see note above
   .ls-match) where the same effect is faked to avoid per-card blur cost.
   ═══════════════════════════════════════════════════════════════════════ */
#ls-app .ls-bar{
  background:rgba(19,23,40,.62);
  backdrop-filter:blur(18px) saturate(150%);
  -webkit-backdrop-filter:blur(18px) saturate(150%);
  border-bottom:1px solid rgba(255,255,255,.08);
  box-shadow:0 1px 0 rgba(var(--accent-rgb),.12);
}
#ls-app .ls-panel{
  background:rgba(19,23,40,.5);
  backdrop-filter:blur(16px) saturate(140%);
  -webkit-backdrop-filter:blur(16px) saturate(140%);
  border:1px solid rgba(255,255,255,.09);
  box-shadow:
    0 0 0 1px rgba(var(--accent2-rgb),.10),
    inset 0 1px 0 rgba(255,255,255,.05),
    0 8px 28px rgba(0,0,0,.3);
}
#ls-app .ls-modal-card{
  background:rgba(19,23,40,.78);
  backdrop-filter:blur(24px) saturate(150%);
  -webkit-backdrop-filter:blur(24px) saturate(150%);
  border:1px solid rgba(255,255,255,.1);
  box-shadow:
    0 0 0 1px rgba(var(--accent-rgb),.25),
    0 0 60px rgba(var(--accent-rgb),.15),
    0 20px 60px rgba(0,0,0,.55);
}
#ls-app .ls-board{
  background:rgba(19,23,40,.5);
  backdrop-filter:blur(16px) saturate(140%);
  -webkit-backdrop-filter:blur(16px) saturate(140%);
}

/* Light theme: dark frosted glass reads muddy on a white page — go the other
   direction (bright frosted glass) instead of forcing the dark recipe. */
#ls-app[data-theme="light"] .ls-bar,
#ls-app[data-theme="light"] .ls-panel,
#ls-app[data-theme="light"] .ls-board{
  background:rgba(255,255,255,.72);
}
#ls-app[data-theme="light"] .ls-modal-card{ background:rgba(255,255,255,.85); }
#ls-app[data-theme="light"] .ls-match{
  background:
    linear-gradient(180deg, rgba(255,255,255,.5), rgba(255,255,255,.1) 40%),
    linear-gradient(180deg, var(--panel2), var(--node));
  border:1px solid rgba(15,23,42,.08);
}

/* Ambient glow around the whole app shell so it reads as "lit" even before
   any match is decided — the neon-HUD identity from the reference brief. */
#ls-app{
  box-shadow:
    0 0 0 1px rgba(var(--accent-rgb),.18),
    0 0 70px rgba(var(--accent2-rgb),.10);
}

/* ═══════════ Export-safe rendering ═══════════
   html2canvas rasterises the stage for PNG/clipboard export, and it does NOT
   honour alpha in gradient colour stops — it paints
   `linear-gradient(90deg, rgba(74,222,128,.13), transparent 70%)` as SOLID
   rgb(74,222,128). On a winner row that turned a 13% wash into a bright green
   slab, and since the winner's name is also green (#4ade80) the team names
   disappeared entirely in every exported bracket.

   Rather than dull the on-screen design, `.ls-exporting` is added to #ls-app
   only while the canvas is being captured: every risky alpha-gradient becomes
   a solid pre-blended colour that rasterises identically, and the winner row
   keeps a clear green identity via a flat tint plus its existing inset bar.
   Nothing here affects normal viewing. */
#ls-app.ls-exporting .ls-match{
  background:var(--node);
  border:1px solid var(--border);
  box-shadow:none;
}
#ls-app.ls-exporting .ls-slot:hover:not(.ls-empty-slot){ background:transparent; }
#ls-app.ls-exporting .ls-slot.ls-won{
  /* Flat equivalent of the winner wash composited over the card. The values
     come from --ls-x-won-bg / --ls-x-won-fg, which JS computes by blending
     the CURRENT --win over the CURRENT card colour and then picking a text
     colour with real contrast against the result.

     This was previously a hardcoded #16281f, which was a dark-green slab:
     wrong on the light theme, and wrong for any custom winner colour. The
     fallbacks below only apply if JS did not run. */
  background:var(--ls-x-won-bg, #16281f);
  box-shadow:inset 3px 0 0 var(--win);
}
#ls-app.ls-exporting .ls-slot.ls-won .ls-name,
#ls-app.ls-exporting .ls-slot.ls-won .ls-seed{ color:var(--ls-x-won-fg, var(--win)); }
#ls-app.ls-exporting .ls-slot.ls-won .ls-score{
  background:var(--win); color:var(--ls-x-on-win, #06210f); border-color:var(--win);
}
#ls-app.ls-exporting .ls-champ{
  background:var(--accent2);
  color:#fff;
}
/* Interactive-only affordances are noise in a static image. */
/* MUST be visibility, not display. `display:none` removes these from flow,
   which reflows every card header, shifts the slots inside each card, and
   leaves the connector SVG — positioned once from pre-export geometry —
   pointing at empty space. That is the detached-lines bug in exported PNGs. */
#ls-app.ls-exporting .ls-mbtn,
#ls-app.ls-exporting .ls-mhead-status{ visibility:hidden; }
#ls-app.ls-exporting .ls-bar,
#ls-app.ls-exporting .ls-panel,
#ls-app.ls-exporting .ls-board,
#ls-app.ls-exporting .ls-modal-card{ backdrop-filter:none; -webkit-backdrop-filter:none; }
#ls-app.ls-exporting{ box-shadow:none; }

/* ═══════════ Toolbar controls, panels, labels (v2.1) ═══════════ */

/* Screen-reader-only text for icon buttons. */
#ls-app .ls-sr{
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0;
}

/* ---- Toolbar action buttons ----
   Customize previously inherited raw .ls-btn padding, so it rendered taller
   than the Format select beside it and sat off the shared baseline. These two
   buttons are a matched pair: same height as .ls-input, same alignment, and a
   clear active state so it's obvious when the panel is open. */
#ls-app .ls-tool-btn{
  display:inline-flex; align-items:center; gap:7px; flex:none;
  height:38px; padding:0 14px; white-space:nowrap;
  font-size:12.5px; font-weight:700; letter-spacing:.02em;
  align-self:flex-end;   /* sits on the select's baseline, not the label's */
}
#ls-app .ls-tool-btn-ico{ font-size:13px; line-height:1; }
#ls-app .ls-tool-btn:hover{ border-color:var(--accent); color:var(--text); }
#ls-app .ls-customize-btn[aria-expanded="true"],
#ls-app .ls-tool-btn[aria-pressed="true"]{
  background:linear-gradient(135deg, rgba(var(--accent2-rgb),.9), rgba(var(--accent-rgb),.9));
  border-color:transparent; color:#08080c;
  box-shadow:0 0 18px rgba(var(--accent-rgb),.35);
}

/* ---- Customize flyout ----
   The panel is a child of .ls-bar, but .ls-bar had no z-index of its own, so
   the toolbar and the panel painted in the same stacking level as the body
   below — and .ls-stage-tools (opaque, later in source order) covered the
   flyout. Raising .ls-bar itself, not just the panel, is what fixes it:
   z-index on the panel alone can't lift it out of an ancestor that sits
   below its sibling. */
#ls-app .ls-bar{ z-index:60; }
#ls-app .ls-customize-panel{ z-index:70; }

/* ---- Collapsible side panels ---- */
#ls-app .ls-rail{ transition:width .18s var(--ls-ease, ease); }
#ls-app .ls-panel-head{ display:flex; align-items:center; gap:8px; }
#ls-app .ls-panel-head .ls-h{ flex:1 1 auto; min-width:0; }
#ls-app .ls-collapse{
  flex:none; width:26px; height:26px; display:grid; place-items:center;
  background:transparent; border:1px solid var(--border); border-radius:7px;
  color:var(--muted); cursor:pointer; font-size:11px; line-height:1; padding:0;
  transition:transform .18s ease, color .12s, border-color .12s;
}
#ls-app .ls-collapse:hover{ color:var(--text); border-color:var(--accent); }
#ls-app .ls-collapse[aria-expanded="false"]{ transform:rotate(-90deg); }
#ls-app .ls-panel[data-collapsed] .ls-panel-body{ display:none; }
#ls-app .ls-panel[data-collapsed]{ padding-bottom:10px; }

/* Rail toggle: a slim always-available handle so the whole sidebar can be
   dismissed to give the bracket the full width. */
#ls-app .ls-rail-toggle{
  position:absolute; top:calc(var(--ls-bar-h, 62px) + 10px); left:10px; z-index:40;
  width:30px; height:30px; display:none; place-items:center;
  background:var(--panel2); color:var(--text);
  border:1px solid var(--border); border-radius:8px; cursor:pointer;
  font-size:14px; line-height:1; padding:0;
}
#ls-app .ls-rail-toggle:hover{ border-color:var(--accent); }
#ls-app[data-rail="hidden"] .ls-rail{ display:none; }
#ls-app[data-rail="hidden"] .ls-rail-toggle{ display:grid; }

/* ---- Participants box ----
   The 168px default showed barely two names of an eight-team roster, which
   made the most important input on the page feel like an afterthought. */
#ls-app .ls-textarea{ min-height:230px; }

/* ---- Stage / round labels ----
   Round names are the bracket's structural signposts and were rendered at
   10px muted grey — effectively invisible against the cards. Give them the
   display face, real size, and an accent rule so each column announces
   itself. They are also click-to-rename (see bracket.js). */
#ls-app .ls-rlabel{
  font-family:var(--ls-display);
  font-size:12px; font-weight:800; letter-spacing:.16em;
  color:var(--text); line-height:1;
  padding:5px 9px 5px 0; cursor:text; border-radius:6px;
  border-bottom:2px solid rgba(var(--accent-rgb),.55);
  transition:color .12s, border-color .12s, background .12s;
}
#ls-app .ls-rlabel:hover{
  color:var(--accent); border-bottom-color:var(--accent);
  background:rgba(var(--accent-rgb),.08);
}
#ls-app .ls-rlabel[data-renamed="1"]{ color:var(--accent); }
/* The rename affordance is interactive chrome; keep it out of exports. */
#ls-app.ls-exporting .ls-rlabel{
  background:transparent; cursor:default;
}

/* ---- Per-match edit affordance ----
   At 15px in --muted on a busy card the pencil was invisible until hovered,
   so the ability to set streams/scores went undiscovered. Larger, bordered,
   and always at readable contrast. */
#ls-app .ls-mbtn{
  width:19px; height:19px; border-radius:5px;
  border:1px solid var(--border); color:var(--text);
  font-size:10.5px; opacity:.85;
}
#ls-app .ls-mbtn:hover{
  color:#08080c; background:var(--accent); border-color:var(--accent); opacity:1;
}

/* ---- Native fullscreen ----
   :fullscreen only matches the element the browser promoted, so size the app
   to the screen and let the viewport claim the leftover height. */
#ls-app:fullscreen{
  width:100vw; height:100vh; display:flex; flex-direction:column;
  border-radius:0; box-shadow:none; overflow:hidden;
}
#ls-app:fullscreen .ls-body{ flex:1 1 auto; min-height:0; }
#ls-app:fullscreen .ls-stage-wrap{ display:flex; flex-direction:column; min-height:0; }
#ls-app:fullscreen .ls-viewport{ flex:1 1 auto; height:auto !important; min-height:0 !important; }
#ls-app:fullscreen .ls-rail{ overflow-y:auto; }

/* Collapse-the-whole-rail control, injected into the first panel header. */
#ls-app .ls-rail-hide{ transform:none; }
#ls-app .ls-rail-hide:hover{ color:var(--accent); border-color:var(--accent); }

/* Admin-configured engine logo replaces the diamond + wordmark lockup. */
#ls-app .ls-logo-img{
  height:26px; width:auto; max-width:190px; object-fit:contain; display:block; flex:none;
}

/* ═══════════ Winner colour + background watermark (v2.2) ═══════════ */

/* Winner highlight is driven by --win so one custom property restyles every
   winner row, seed, name and score chip at once — including in exports. */
#ls-app .ls-colorwrap{ display:flex; align-items:center; gap:6px; }
#ls-app .ls-color{
  width:44px; height:34px; padding:2px; cursor:pointer;
  background:var(--panel2); border:1px solid var(--border); border-radius:8px;
}
#ls-app .ls-range{ width:100%; accent-color:var(--accent); cursor:pointer; padding:0; height:34px; }

/* Full-bleed background watermark: sits behind the connectors and cards, is
   part of .ls-stage so it is captured in PNG exports exactly as seen. */
#ls-app .ls-bgwm-layer{
  position:absolute; inset:0; z-index:0; pointer-events:none;
  background-repeat:no-repeat; background-position:center;
  background-size:min(58%, 620px) auto;
  opacity:0;
}
#ls-app .ls-links{ z-index:1; }
#ls-app .ls-nodes{ z-index:2; }
#ls-app .ls-stage-title{ z-index:3; }

/* ═══════════ Social share row (v2.5) ═══════════
   One row, never wrapping: the buttons are fixed-size icons and the row
   scrolls horizontally on very narrow rails instead of breaking into a
   ragged second line. Each carries its own brand colour so the row reads
   as recognisable logos rather than uniform grey chips. */
#ls-app .ls-share{ display:flex; flex-direction:column; gap:7px; margin-top:4px; }
/* Grid, not flex: seven fixed-width buttons overflowed the 288px rail and
   the last ones were clipped. Equal fractional columns let each button
   shrink to whatever the rail allows, so the row always fits on one line at
   any rail width and nothing is ever cut off. */
#ls-app .ls-share-row{
  display:flex; flex-wrap:nowrap; gap:6px;
}
/* flex:0 1 36px — never grow past icon size (a grid of 1fr columns blew them
   up to 158px in a wide panel), but allowed to SHRINK so seven icons always
   fit the 288px rail on one line instead of being clipped. */
#ls-app .ls-share-btn{
  flex:0 1 36px; width:36px; min-width:22px; aspect-ratio:1; height:auto;
  display:grid; place-items:center;
  cursor:pointer; padding:0; color:#fff;
  background:var(--ls-brand, var(--panel2));
  border:1px solid transparent; border-radius:var(--r);
  transition:transform .1s, filter .15s, box-shadow .15s;
}
#ls-app .ls-share-btn svg{ width:56%; height:56%; max-width:18px; max-height:18px; display:block; }
#ls-app .ls-share-btn:hover{ filter:brightness(1.12); transform:translateY(-1px); }
#ls-app .ls-share-btn:active{ transform:translateY(1px); }
#ls-app .ls-share-btn:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }

/* Official brand colours. Instagram is its real gradient, not a flat pink. */
#ls-app .ls-share-x        { --ls-brand:#000000; }
#ls-app .ls-share-facebook { --ls-brand:#1877F2; }
#ls-app .ls-share-whatsapp { --ls-brand:#25D366; }
#ls-app .ls-share-telegram { --ls-brand:#229ED9; }
#ls-app .ls-share-reddit   { --ls-brand:#FF4500; }
#ls-app .ls-share-instagram{
  background:radial-gradient(circle at 30% 107%,#fdf497 0%,#fd5949 45%,#d6249f 60%,#285AEB 90%);
}
#ls-app .ls-share-copy     { --ls-brand:var(--panel2); color:var(--text); border-color:var(--border); }
#ls-app .ls-share-copy:hover{ border-color:var(--accent); }
/* X on a near-black canvas needs an edge to stay visible. */
#ls-app[data-theme="matrix"] .ls-share-x,
#ls-app[data-theme="cyber"]  .ls-share-x{ border-color:var(--border-hi); }
#ls-app[data-theme="light"]  .ls-share-copy{ background:#fff; border-color:#cfd6e4; }

/* ═══════════ Light-theme polish ═══════════
   The shared control styles were tuned against dark panels; on white they
   need real borders and darker text rather than the same low-contrast greys. */
#ls-app[data-theme="light"] .ls-btn{
  background:#ffffff; border-color:#cfd6e4; color:#141824;
}
#ls-app[data-theme="light"] .ls-btn:hover{ border-color:#2563eb; background:#f5f8ff; }
#ls-app[data-theme="light"] .ls-btn-ghost{ background:transparent; }
#ls-app[data-theme="light"] .ls-btn-primary{
  background:linear-gradient(135deg,#2563eb,#0ea5e9); color:#ffffff; border-color:transparent;
}
#ls-app[data-theme="light"] .ls-input{ background:#ffffff; border-color:#cfd6e4; }
#ls-app[data-theme="light"] .ls-panel{ box-shadow:0 1px 2px rgba(20,24,36,.05); }
#ls-app[data-theme="light"] .ls-match{ box-shadow:0 1px 3px rgba(20,24,36,.08); }
#ls-app[data-theme="light"] .ls-customize-panel{ box-shadow:0 16px 40px rgba(20,24,36,.16); }

/* ═══════════ Toolbar alignment + control polish (v2.6) ═══════════
   The Format control is a stacked label+select, so it is taller than the
   plain buttons beside it. Bottom-aligning the row and matching the button
   height to the input's makes the three controls read as one set instead of
   three different sizes. */
#ls-app .ls-bar-right{ align-items:flex-end; }
#ls-app .ls-bar-right .ls-tool-btn{
  height:36px; display:inline-flex; align-items:center; justify-content:center; gap:7px;
}
#ls-app .ls-bar-right .ls-input{ height:36px; }

/* Buttons centre their label properly. `text-align:center` alone does not
   centre when the button is a flex item that has been stretched, which is
   why "Generate Bracket" sat slightly left of centre. */
#ls-app .ls-btn{
  display:inline-flex; align-items:center; justify-content:center; gap:6px;
}

/* ═══════════ Optional grid backdrop ═══════════
   Off by default. Sits inside .ls-stage so it is captured in PNG exports
   exactly as shown, and behind both the connectors and the cards. */
#ls-app .ls-grid-layer{
  position:absolute; inset:0; z-index:0; pointer-events:none; display:none;
  background-image:
    linear-gradient(var(--grid-line) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid-line) 1px, transparent 1px);
  background-size:var(--grid-size, 40px) var(--grid-size, 40px);
}
#ls-app[data-grid="on"] .ls-grid-layer{ display:block; }
#ls-app{ --grid-line:rgba(255,255,255,.05); }
#ls-app[data-theme="light"]{ --grid-line:rgba(20,24,36,.07); }
#ls-app .ls-layer{ z-index:2; }
#ls-app .ls-bgwm-layer{ z-index:1; }

/* Real LeagueSpawn mark in the tool header (was a ◆ glyph placeholder).
   width/height are set on the element too so it reserves its box before
   decode and cannot shift the toolbar. */
#ls-app .ls-logo-mark{ width:32px; height:32px; flex:none; display:block; object-fit:contain; }

/* ═══════════ Theme swatch bar (above the app) ═══════════ */
.ls-themebar{
  display:flex; align-items:center; gap:10px; flex-wrap:wrap;
  margin:0 0 -14px;                    /* tucks against the app's own top margin */
  font-family:'Inter',ui-sans-serif,system-ui,'Segoe UI',sans-serif;
}
.ls-themebar-label{
  font-size:10px; font-weight:700; letter-spacing:1.2px; text-transform:uppercase;
  color:var(--wp--preset--color--muted, #9aa1b8);
}
.ls-themebar-row{ display:flex; gap:6px; flex-wrap:wrap; }
.ls-swatch{
  display:inline-flex; align-items:center; gap:7px;
  padding:6px 11px 6px 7px; cursor:pointer;
  font:inherit; font-size:12px; font-weight:600;
  color:var(--wp--preset--color--muted, #9aa1b8);
  background:var(--wp--preset--color--surface, #131728);
  border:1px solid var(--wp--preset--color--border, #232842);
  border-radius:999px;
  transition:color .14s, border-color .14s, transform .08s;
}
.ls-swatch:hover{ color:var(--wp--preset--color--foreground, #e8eaf2); border-color:var(--wp--preset--color--accent, #22d3ee); }
.ls-swatch:active{ transform:translateY(1px); }
.ls-swatch:focus-visible{ outline:2px solid var(--wp--preset--color--accent, #22d3ee); outline-offset:2px; }
.ls-swatch.is-active{
  color:var(--wp--preset--color--foreground, #e8eaf2);
  border-color:var(--wp--preset--color--accent, #22d3ee);
  background:var(--wp--preset--color--surface-2, #171c30);
}
.ls-swatch-dot{
  width:16px; height:16px; flex:none; border-radius:50%;
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.18);
}
/* Small screens: swatches become a single scrolling row of dots. The names
   are dropped rather than wrapped, which would otherwise push the bracket
   itself below the fold on a phone. */
@media (max-width:700px){
  .ls-themebar{ gap:7px; }
  .ls-themebar-row{ flex-wrap:nowrap; overflow-x:auto; scrollbar-width:none; }
  .ls-themebar-row::-webkit-scrollbar{ display:none; }
  .ls-swatch{ padding:6px; }
  .ls-swatch-name{ display:none; }
}
