/*! ES-BUILD 5.5 */
/* ==========================================================================
   Eskimeyen Şarkılar — front-end stylesheet ("Sahne" design system, Phase 10)
   Layers: tokens → base → components → layout → utilities.
   Fonts come from fonts.css; the accent tones and font families are injected per site by
   brand_style() as --brand-* / --display / --body. The accent is the brand's and never moves;
   a cover palette only tints the surfaces that show that cover (--c1..--c3, set inline on cards
   and song pages, and by app.js on the dock).
   ========================================================================== */

/* ---------- Tokens (dark = home theme, warm neutrals) ---------- */
:root{
  --bg:#1b1715; --bg2:#231d19; --bg3:#2c251f; --bg4:#382f28;
  --line:rgba(246,239,230,.08); --line-2:rgba(246,239,230,.15);
  /* --dim was #6f6357 (dark) and #9a8d80 (light), which measured 2.6-3.2:1 against every surface it
     is drawn on — against a 4.5:1 requirement for body text. It is not a decoration colour here: it
     paints the hint line under almost every form field, the labels of the entire phone tab bar, the
     resting state of every floating label on the site, and — on a timed song — EVERY lyric line
     until it becomes the current one. Raised to 4.8-5.6:1 in both themes, computed against --bg,
     --bg2 and --bg3. It now sits closer to --muted than it did, which costs a little of the
     three-step hierarchy; unreadable text costs more. */
  /* --dim was #6f6357 (dark) and #9a8d80 (light), which measured 2.6-3.2:1 against every surface it
     is drawn on, against a 4.5:1 requirement for body text. It is not a decoration colour here: it
     paints the hint line under almost every form field, the labels of the entire phone tab bar, the
     resting state of every floating label on the site, and — on a timed song — EVERY lyric line
     until it becomes the current one. Raised to 4.8-5.6:1 in both themes, computed against --bg,
     --bg2 and --bg3. It sits closer to --muted than it did, which costs a little of the three-step
     hierarchy; unreadable text costs more. */
  --text:#f6efe6; --muted:#a8998a; --dim:#9b8f7e;
  --brand-acc-dark:#ef9f4a; --brand-acc-light:#a4590a; --brand-acc2-dark:#b8503a; --brand-acc2-light:#7a2f1c;
  --brand-ink-dark:#1b1715; --brand-ink-light:#ffffff;
  --ink:var(--brand-ink-dark);
  /* --acc is the accent as TEXT: fitted to read on this page's ground. --fill is the same hue
     deepened until --ink reads on IT, and belongs only on a filled control. They are the same value
     for an amber or a green brand and diverge for a coral or a blue one. */
  --acc:var(--brand-acc-dark); --fill:var(--brand-fill-dark,var(--brand-acc-dark));
  --acc2:var(--brand-acc2-dark); --acc3:#2a1a24; --acc-text:var(--acc);
  --logo-bg:var(--acc); --logo-fg:var(--ink);
  --ok:#5fc48f; --warn:#e5b04c; --bad:#e76f6f;
  --display:"Bricolage Grotesque",system-ui,-apple-system,"Segoe UI",sans-serif;
  --body:"Figtree",system-ui,-apple-system,"Segoe UI",sans-serif;
  --glass:rgba(27,23,21,.74); --glass-2:rgba(35,29,25,.8);
  --shadow:0 30px 60px -30px rgba(0,0,0,.8);
  /* NOT `none`. This token is used as the last item of comma-separated box-shadow lists
     (`inset 0 0 0 1px var(--line),var(--card-shadow)`), and `none` is not a legal shadow INSIDE a
     list — it is only legal as the whole value. One invalid item invalidates the entire declaration,
     so with `none` here every .work, .tile, .card-form and .card-song computed `box-shadow: none` in
     the dark theme: no 1px outline, and no state stripe either. Measured, not guessed. A fully
     transparent shadow is the way to say "nothing" in a position where `none` cannot be said. */
  --card-shadow:0 0 #0000;
  --r-ctl:10px; --r-sm:14px; --r:20px; --r-lg:24px;
  --ease:cubic-bezier(.2,.8,.2,1);
  --dock-h:76px; --tab-h:64px;
  /* The sleeve, on a phone: the feed card and the work page both size their cover from this, so the
     two screens always agree. Two axes and no fixed pixel size, because a phone is never simply
     "small" or "large" — it is narrow or short, and which of the two bites first is different on
     every device. 62vw is the one that loses on a wide screen (S26 Ultra), 42svh the one that loses
     on a tall narrow one (iPhone mini), and 300px exists only so a tablet is not handed a record
     sleeve the size of a plate. The old rule capped it at a flat 224–236px, which is why the same
     cover looked right on one phone and lost on another: on a 450px-wide screen the cap was doing
     all the work and the art was a quarter smaller than the screen could carry.
     svh, never vh: on iOS vh measures the viewport you would have if the browser chrome were hidden,
     which is not the viewport anybody is looking at. The upgrade is behind @supports below rather
     than a second declaration here — a custom property stores raw tokens, so a browser that has
     never heard of svh would accept the declaration anyway and only fail later, at the point of use,
     leaving height:auto and a broken card. @supports asks the question before, not after. */
  --cover: min(62vw, 42vh, 300px);
  /* The tab bar is --tab-h of controls PLUS the home-indicator strip underneath them. Everything that
     sits above it has to use this, not --tab-h: the player used --tab-h + the inset while the bar
     itself was only --tab-h tall, which on any iPhone left a gap exactly the height of the indicator
     between the two — the page showing through between the player and the tabs. */
  --tab-total:calc(var(--tab-h) + env(safe-area-inset-bottom));
  --max:1240px;
}
@supports (height:1svh){:root{--cover: min(62vw, 42svh, 300px)}}

/* ---------- Light theme ----------
   Dark is the design's home; light is a full second palette (cream paper), not an inversion.
   Order of precedence: data-theme attribute (user choice, stored in cookie) > OS preference > dark. */
:root[data-theme="light"]{
  --ink:var(--brand-ink-light); --fill:var(--brand-fill-light,var(--brand-acc-light));
  --glass:rgba(246,241,234,.8); --glass-2:rgba(255,253,249,.86);
  --shadow:0 20px 50px -30px rgba(34,28,24,.35);
  --acc:var(--brand-acc-light); --acc2:var(--brand-acc2-light); --acc-text:var(--brand-acc-light);
  --logo-bg:#221c18; --logo-fg:var(--brand-acc-light);
  --bg:#f6f1ea; --bg2:#fffdf9; --bg3:#efe7dc; --bg4:#e3d8c9;
  --text:#221c18; --muted:#6b5f54; --dim:#6e6354;
  --line:rgba(34,28,24,.08); --line-2:rgba(34,28,24,.16);
  --card-shadow:0 1px 2px rgba(34,28,24,.04),0 12px 32px -18px rgba(34,28,24,.25);
}
@media (prefers-color-scheme:light){
:root:not([data-theme="dark"]){
  --ink:var(--brand-ink-light); --fill:var(--brand-fill-light,var(--brand-acc-light));
  --glass:rgba(246,241,234,.8); --glass-2:rgba(255,253,249,.86);
  --shadow:0 20px 50px -30px rgba(34,28,24,.35);
  --acc:var(--brand-acc-light); --acc2:var(--brand-acc2-light); --acc-text:var(--brand-acc-light);
  --logo-bg:#221c18; --logo-fg:var(--brand-acc-light);
  --bg:#f6f1ea; --bg2:#fffdf9; --bg3:#efe7dc; --bg4:#e3d8c9;
  --text:#221c18; --muted:#6b5f54; --dim:#6e6354;
  --line:rgba(34,28,24,.08); --line-2:rgba(34,28,24,.16);
  --card-shadow:0 1px 2px rgba(34,28,24,.04),0 12px 32px -18px rgba(34,28,24,.25);
}}

/* ---------- Base ---------- */
*,*::before,*::after{box-sizing:border-box}
[hidden]{display:none!important}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth}
body{margin:0;background:var(--bg);color:var(--text);font:15px/1.55 var(--body);-webkit-font-smoothing:antialiased;min-height:100vh;overflow-x:hidden}
body.front{padding-bottom:calc(var(--dock-h) + var(--tab-total))}
@media (min-width:900px){body.front{padding-bottom:var(--dock-h)}}
body.front:not(.has-dock){padding-bottom:var(--tab-total)}
@media (min-width:900px){body.front:not(.has-dock){padding-bottom:0}}
a{color:inherit;text-decoration:none}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer;padding:0}
img,svg{display:block;max-width:100%}
input,textarea,select{font:inherit;color:inherit}
:focus-visible{outline:2px solid var(--acc);outline-offset:2px;border-radius:4px}
.skip{position:absolute;left:-999px;top:8px;background:var(--fill);color:var(--ink);padding:8px 12px;border-radius:8px;z-index:100}
.skip:focus{left:8px}
h1,h2,h3{font-family:var(--display);letter-spacing:-.02em;line-height:1.1;margin:0;text-wrap:balance}
.h-display{font-size:clamp(30px,5vw,52px);font-weight:700}
.h-display em{font-style:normal;color:var(--acc);transition:color .8s}
.h3{font-size:17px;font-weight:600}
.lead{color:var(--muted);font-size:16px;max-width:56ch;margin:10px 0 0}
.muted{color:var(--muted)}
code{font:.9em ui-monospace,SFMono-Regular,Menlo,monospace;background:var(--bg3);padding:1px 6px;border-radius:6px}

/* Icons (Material Symbols Rounded, inline via icon() helper; fill=currentColor) */
.i{width:22px;height:22px;flex:none}
.i.s{width:18px;height:18px}
.i.xs{width:14px;height:14px}
.ic-on{display:none}.on>.ic-on,.on .ic-on{display:block}.on>.ic-off,.on .ic-off{display:none}
.tabular{font-variant-numeric:tabular-nums}
.eyebrow-acc{color:var(--acc-text,var(--acc))}

/* ---------- Header ---------- */
.hdr{position:sticky;top:0;z-index:40;background:var(--glass);backdrop-filter:blur(18px) saturate(1.4);-webkit-backdrop-filter:blur(18px) saturate(1.4);border-bottom:1px solid var(--line)}
.hdr-in{max-width:var(--max);margin:0 auto;padding:10px 18px;display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:14px;min-height:64px}
@media (min-width:900px){.hdr-in{padding:10px 28px;grid-template-columns:1fr minmax(280px,520px) 1fr;gap:24px}}
.brand{display:inline-flex;align-items:center;gap:9px;justify-self:start;min-width:0}
.brand-mark{width:36px;height:36px;flex:none;display:block}
.brand-mark svg{width:36px;height:36px;display:block;transition:transform .25s var(--ease)}
.brand:hover .brand-mark svg{transform:scale(1.05)}
/* One line, two weights: the wordmark used to be a two-line block whose lines were letter-spaced to
   match — heavy on desktop and too wide for a 390px header. */
.brand-text{display:flex;flex-direction:column;justify-content:space-between;height:36px;font-family:var(--display);font-weight:700;font-size:14.5px;line-height:1;letter-spacing:.05em;text-transform:uppercase;min-width:0}
.brand-text span{display:block;width:max-content;white-space:nowrap}
.brand-text span:first-child{color:var(--text)}
.brand-text span:last-child{color:var(--acc);transition:color .8s}
@media (max-width:899.98px){.brand-mark,.brand-mark svg{width:32px;height:32px}.brand-text{height:32px;font-size:12.5px;letter-spacing:.04em}}
.search{display:flex;align-items:center;gap:8px;background:var(--bg2);box-shadow:inset 0 0 0 1px var(--line);border-radius:var(--r-ctl);padding:10px 14px;color:var(--muted);min-width:0;justify-self:stretch}
.search:focus-within{box-shadow:inset 0 0 0 1.5px var(--acc)}
.search input{flex:1;background:none;border:0;outline:0;color:var(--text);min-width:0}
.search input::placeholder{color:var(--dim)}
.hdr-nav{display:flex;align-items:center;gap:8px;justify-self:end;height:40px}
.hdr-nav>*{height:40px;display:inline-flex;align-items:center;justify-content:center}
.hdr-nav .btn-accent,.hdr-nav .btn-ghost{padding-top:0;padding-bottom:0}
/* Below 900px the tab bar exists and carries its own + as the middle, thumb-height control. Keeping
   this one as well put three identical upload buttons on one 400px screen — header, page heading and
   tab bar. The tab bar's is the one that is on every page, so this is the one that goes. */
@media (max-width:899.98px){.hdr-nav .btn-up{display:none}}
.hdr-nav .menu{display:block}
.hdr-nav .btn-accent span{display:none}
@media (min-width:900px){.hdr-nav .btn-accent span{display:inline}}
/* The header's field only; the search SHEET uses the same class and must stay visible on phones. */
@media (max-width:639.98px){.hdr .search{display:none}.hdr-in{grid-template-columns:auto 1fr}.hdr-nav .btn-ghost{display:none}}
@media (max-width:899.98px){.hdr-nav .btn-accent{padding-left:11px;padding-right:11px}}
.icb{width:40px;height:40px;border-radius:var(--r-ctl);display:inline-flex;align-items:center;justify-content:center;background:var(--bg2);box-shadow:inset 0 0 0 1px var(--line);color:var(--muted);flex:none}
.icb:hover{color:var(--text);background:var(--bg3)}
.icb .on-dark,.icb .on-light{line-height:0}
.avatar-sm{width:40px;height:40px;font-size:15px;cursor:pointer;border-radius:var(--r-ctl)}

/* Buttons */
.btn-accent,.btn-ghost{display:inline-flex;align-items:center;justify-content:center;gap:8px;padding:10px 16px;border-radius:var(--r-ctl);font-weight:600;font-size:14px;transition:transform .2s var(--ease),background .8s,opacity .2s;white-space:nowrap}
.btn-accent{background:var(--fill)!important;color:var(--ink)!important;box-shadow:0 10px 24px -10px var(--fill),inset 0 0 0 1px rgba(255,255,255,.25)}
.btn-accent:hover{transform:translateY(-1px)}
.btn-ghost{box-shadow:inset 0 0 0 1px var(--line-2);color:var(--text)!important}
.btn-ghost:hover{background:var(--bg3)}
.btn-accent.sm,.btn-ghost.sm{padding:7px 12px;font-size:13px}
.btn-accent:disabled{opacity:.5;cursor:not-allowed;transform:none}

/* ---------- Page ---------- */
.page{max-width:var(--max);margin:0 auto;padding:24px 18px 40px}
@media (min-width:900px){.page{padding:32px 28px 60px}}
.hero-row{display:flex;align-items:flex-end;justify-content:space-between;gap:20px;margin-bottom:22px}
.notice{padding:12px 16px;border-radius:var(--r-ctl);margin-bottom:18px;background:var(--bg3);box-shadow:inset 0 0 0 1px var(--line-2)}
.notice-success{box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--ok) 50%,transparent)}
.notice-error{box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--bad) 50%,transparent)}

/* Chips */
.chipbar{display:flex;gap:8px;overflow-x:auto;padding:2px 0 14px;scrollbar-width:none;-webkit-overflow-scrolling:touch}
.chipbar::-webkit-scrollbar{display:none}
.chip{display:inline-flex;align-items:center;gap:7px;padding:8px 14px;border-radius:var(--r-ctl);background:var(--bg2);box-shadow:inset 0 0 0 1px var(--line);color:var(--muted);font-size:13.5px;font-weight:500;white-space:nowrap;transition:background .2s,color .2s}
.chip:hover{color:var(--text)}
.chip.on{color:var(--text);background:color-mix(in srgb,var(--acc) 18%,var(--bg2));box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--acc) 55%,transparent)}
/* Origin monogram — deliberately quiet */
/* .og was the origin-type monogram (İ / İ+ / YZ). Migration 0034 removed origin types, but the
   badge shape is still used for the # marker in Admin > Tags, so the base rule stays. */
.og{display:inline-grid;place-items:center;min-width:18px;height:18px;padding:0 4px;border-radius:var(--r-ctl);font:700 10px/1 var(--display);letter-spacing:.02em;color:var(--muted);box-shadow:inset 0 0 0 1px var(--line-2)}

/* ---------- Bento ---------- */
.bento{display:grid;gap:12px;grid-template-columns:repeat(2,1fr)}
@media (min-width:900px){.bento{grid-template-columns:repeat(4,1fr);grid-auto-rows:minmax(150px,auto)}
  .tile-lg{grid-column:span 2;grid-row:span 2}.tile-md{grid-column:span 2}}
.tile{position:relative;border-radius:var(--r);background:var(--bg2);box-shadow:inset 0 0 0 1px var(--line);padding:18px;display:flex;flex-direction:column;gap:6px;transition:transform .25s var(--ease),box-shadow .25s}
.tile:hover{transform:translateY(-2px);box-shadow:inset 0 0 0 1px var(--line-2)}
.tile.empty{justify-content:center;min-height:120px}
.tile-lg{grid-column:span 2}
.tile-sm{justify-content:center;align-items:flex-start}
.stat-n{font:600 30px/1 var(--display);font-variant-numeric:tabular-nums;letter-spacing:-.03em}
.stat-l{color:var(--muted);font-size:12.5px;text-transform:uppercase;letter-spacing:.06em}

/* Song card (used by Module 3; defined now so the design system is complete) */
.card-song{position:relative;border-radius:var(--r);overflow:hidden;background:var(--bg2);box-shadow:inset 0 0 0 1px var(--line);isolation:isolate}
.card-song .art{aspect-ratio:1;background:var(--bg3) center/cover;transition:transform .5s var(--ease)}
.card-song:hover .art{transform:scale(1.03)}
.card-song .meta{padding:12px 14px 14px}
/* Two lines, not one line with a cut. At 360px a card is 155px wide and 14 of the 16 text lines in
   an eight-work grid were being ellipsed — "Dolana Ay Dola…", "Şu Dağlar Kömü…". A title is the one
   thing on the card the reader actually needs, and a second line is cheaper than a guess. The
   min-height keeps one-line and two-line cards the same height so the grid rows stay level. */
.card-song .meta b{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;
  font-family:var(--display);font-weight:600;font-size:15px;line-height:1.28;height:2.56em;
  white-space:normal;overflow:hidden;overflow-wrap:anywhere}
.card-song .meta span{display:flex;align-items:center;gap:6px;color:var(--muted);font-size:12.5px;margin-top:2px}
/* Measured from the COVER, not from the card's bottom. `top:calc(100% - 92px)` meant "92px up from
   the bottom of the card", where 92 was tuned against a one-line meta — so the moment titles were
   allowed a second line the meta grew 19px and the button slid down onto the title: 32px of a 44px
   circle sitting on the first line of the words, permanently, because @media (hover:none) keeps it
   visible on every phone. The art is square and as wide as the card, so its lower edge is exactly
   100cqw from the top whatever the meta does underneath, and the button straddles it the way it was
   always meant to. container-type on the card is what makes cqw mean "this card's width". */
.card-song{container-type:inline-size}
.card-song .play{position:absolute;right:12px;top:calc(100cqw - 22px);width:44px;height:44px;border-radius:50%;background:var(--fill);color:var(--ink);display:grid;place-items:center;box-shadow:0 10px 24px -8px var(--fill);opacity:0;transform:translateY(6px);transition:opacity .2s,transform .25s var(--ease)}
.card-song:hover .play,.card-song:focus-within .play,.card-song.playing .play{opacity:1;transform:none}
@media (hover:none){.card-song .play{opacity:1;transform:none}}

/* ---------- Prose & errors ---------- */
.prose{max-width:66ch}
.prose h1{font-size:clamp(26px,4vw,38px);margin-bottom:16px}
.prose p{color:var(--muted);font-size:16px;line-height:1.7}
.prose-legal h2{font-size:18px;margin:26px 0 8px;color:var(--text)}
.prose-legal p{font-size:15px}
.prose-legal a{color:var(--acc-text,var(--acc));font-weight:600}
.prose a{color:var(--acc);text-decoration:underline;text-underline-offset:3px}
.error-page{max-width:560px;margin:8vh auto;text-align:center;display:flex;flex-direction:column;align-items:center;gap:14px}
.error-code{font:800 clamp(64px,14vw,120px)/1 var(--display);letter-spacing:-.05em;background:linear-gradient(135deg,var(--acc),var(--acc2));-webkit-background-clip:text;background-clip:text;color:transparent;margin:0}
.error-page h1{font-size:24px}
.search-lg{width:100%;padding:12px 18px}
.debug{text-align:left;width:100%;overflow:auto;background:var(--bg2);padding:14px;border-radius:var(--r-sm);font-size:12px;color:var(--muted)}

/* ---------- Footer ---------- */
.ftr{border-top:1px solid var(--line);margin-top:40px}
.ftr-in{max-width:var(--max);margin:0 auto;padding:26px 18px;display:flex;flex-wrap:wrap;gap:14px 28px;align-items:center;color:var(--muted);font-size:13.5px}
.ftr-brand{margin:0;font-family:var(--display);font-weight:600;color:var(--text)}
.ftr-brand span{color:var(--dim);font-weight:400;font-family:var(--body)}
.ftr nav{display:flex;flex-wrap:wrap;gap:6px 18px}
.ftr nav a:hover{color:var(--text)}
.ftr-copy{margin:0;margin-left:auto;color:var(--dim)}

/* ---------- Mobile tab bar ---------- */
.tabbar{position:fixed;left:0;right:0;bottom:0;z-index:45;height:var(--tab-total);display:flex;justify-content:space-around;align-items:center;padding:0 6px env(safe-area-inset-bottom);background:var(--glass);backdrop-filter:blur(18px);-webkit-backdrop-filter:blur(18px);border-top:1px solid var(--line)}
.tabbar a{display:flex;flex-direction:column;align-items:center;gap:3px;font-size:10.5px;color:var(--dim);padding:6px 10px;letter-spacing:.02em;min-width:56px}
.tabbar a[aria-current="page"]{color:var(--acc)}
.tabbar .tab-up{gap:4px}
.tabbar .up{width:44px;height:44px;border-radius:15px;background:var(--fill);color:var(--ink);display:grid;place-items:center;margin-top:-14px;box-shadow:0 10px 24px -8px var(--fill);transition:background .8s,margin-top .3s var(--ease)}
/* Raised, it pokes 14px above the bar. That is fine over the page and wrong under the player, where it
   would be half-swallowed by it — so while something is playing it sits down flush. */
body.has-dock .tabbar .up{margin-top:0}
@media (min-width:900px){.tabbar{display:none}}

/* ---------- Modal ---------- */
#modal-root{position:fixed;inset:0;z-index:60;display:grid;place-items:end center;background:rgba(10,11,14,.55);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);padding:0}
@media (min-width:640px){#modal-root{place-items:center;padding:20px}}
.modal{width:100%;max-width:460px;max-height:min(92vh,760px);overflow:auto;background:var(--bg2);border-radius:var(--r-lg) var(--r-lg) 0 0;box-shadow:inset 0 0 0 1px var(--line-2),var(--shadow);padding:22px 20px 26px;animation:sheet .3s var(--ease)}
@media (min-width:640px){.modal{border-radius:var(--r-lg);animation:pop .25s var(--ease)}}
.modal-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px}
.modal-head :is(h1,h2){font-size:20px}
.modal-close{width:34px;height:34px;border-radius:50%;display:grid;place-items:center;color:var(--muted);background:var(--bg3)}
@keyframes sheet{from{transform:translateY(40px);opacity:0}}
@keyframes pop{from{transform:scale(.96);opacity:0}}

/* ---------- Forms ---------- */
.field{margin-top:14px}
/* legend and .field-cap alongside label: the gender radios and the date selects are a <fieldset>
   now, because three controls that answer one question need a group name, and a <label> with no
   `for` and nothing wrapped names nothing at all. */
.field label,.field legend,.field .field-cap{display:block;font-size:12px;color:var(--muted);letter-spacing:.05em;text-transform:uppercase;margin-bottom:6px;font-weight:600}
/* The UA gives a fieldset a groove border, side margins, generous padding and a min-content floor —
   all four would box the two groups and break the date grid. */
.field-set{border:0;margin:14px 0 0;padding:0;min-inline-size:0}
.field-set>legend{padding:0;width:100%}
.inp{width:100%;background:var(--bg3);border:0;box-shadow:inset 0 0 0 1px var(--line);border-radius:var(--r-ctl);padding:12px 14px;color:var(--text);outline:0;transition:box-shadow .2s}
.inp:focus{box-shadow:inset 0 0 0 1.5px var(--acc)}
.inp.invalid{box-shadow:inset 0 0 0 1.5px var(--bad)}
.field-error{color:var(--bad);font-size:12.5px;margin-top:6px}
.radios{display:grid;gap:8px}
.radios label{display:flex;gap:10px;align-items:center;padding:11px 12px;border-radius:var(--r-ctl);background:var(--bg3);box-shadow:inset 0 0 0 1px var(--line);cursor:pointer}
.radios input{accent-color:var(--acc)}
.radios label:has(:checked){box-shadow:inset 0 0 0 1.5px var(--acc);background:color-mix(in srgb,var(--acc) 10%,var(--bg3))}
.cta{margin-top:18px;width:100%;padding:14px;border-radius:var(--r-ctl);background:var(--fill);color:var(--ink);font-weight:700;font-size:15px;box-shadow:0 14px 34px -12px var(--fill);transition:transform .2s var(--ease),opacity .2s,background .8s}
.cta:disabled{opacity:.45;cursor:not-allowed}

/* ---------- Motion ---------- */
@media (prefers-reduced-motion:reduce){*,*::before,*::after{animation:none!important;transition:none!important;scroll-behavior:auto!important}}

/* ---------- Auth & settings (Module 2) ---------- */
.auth-box{max-width:460px;margin:0 auto}
.page > .auth-box{padding:10px 0 30px}
.modal .auth-box{max-width:none}
.auth-box .modal-head :is(h1,h2){font-size:22px}
.auth-alt{color:var(--muted);text-align:center;margin:18px 0 0;font-size:14px}
.auth-alt a,.link{color:var(--acc-text,var(--acc));font-weight:600}
.hint{color:var(--dim);font-size:12.5px;margin:6px 0 0}
.opt{color:var(--dim);font-weight:400;text-transform:none;letter-spacing:0;font-size:11px;margin-left:4px}
.inp-prefix{display:flex;align-items:center;background:var(--bg3);border-radius:var(--r-ctl);box-shadow:inset 0 0 0 1px var(--line)}
.inp-prefix span{padding:0 0 0 14px;color:var(--dim);font-weight:600}
.inp-prefix .inp{box-shadow:none;background:none;padding-left:6px}
.inp-prefix:focus-within{box-shadow:inset 0 0 0 1.5px var(--acc)}
.inp.static{display:flex;align-items:center;gap:10px;color:var(--muted)}
.chk{display:flex;gap:10px;align-items:flex-start;color:var(--muted);font-size:13.5px;margin-top:14px;cursor:pointer}
.chk input{accent-color:var(--acc);margin-top:3px;flex:none}
.chk a{color:var(--acc);text-decoration:underline;text-underline-offset:2px}
.row-between{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:6px}
.row-between .chk{margin-top:0}
.badge{display:inline-flex;align-items:center;padding:3px 9px;border-radius:var(--r-ctl);font-size:11.5px;font-weight:600;background:var(--bg3);color:var(--muted)}
.badge-warn{background:color-mix(in srgb,var(--warn) 18%,transparent);color:var(--warn)}
.badge-ok{background:color-mix(in srgb,var(--ok) 18%,transparent);color:var(--ok)}
.badge-bad{background:color-mix(in srgb,var(--bad) 18%,transparent);color:var(--bad)}
.settings{max-width:720px;display:flex;flex-direction:column;gap:18px}
/* An 80-character single-token query is a valid /search?q=… link, and without this it made the
   heading 1380px wide inside a 360px screen. */
.h-page{font-size:clamp(24px,4vw,32px);overflow-wrap:anywhere}
.tile.empty p{overflow-wrap:anywhere}
.card-form{background:var(--bg2);border-radius:var(--r);box-shadow:inset 0 0 0 1px var(--line);padding:20px}
.card-form .h3{margin-bottom:4px}
.card-form .btn-accent,.card-form .btn-ghost{margin-top:18px}
.grid-2{display:grid;gap:0 14px;grid-template-columns:1fr}
@media (min-width:640px){.grid-2{grid-template-columns:1fr 1fr}}
.radios-row{grid-template-columns:repeat(2,1fr)}
@media (min-width:640px){.radios-row{grid-template-columns:repeat(4,1fr)}}
.radios-row label{padding:10px 12px;font-size:13.5px}
/* Rows, not one wrapping flex line. As `display:flex; align-items:center` with the bio inside the
   middle column, the 84px avatar was centred against a 400px-tall text column and landed 103px BELOW
   the name it belongs to — beside the middle of the biography. Measured at 360px. Now the avatar and
   the name share a row of their own, and the bio, the one part whose height varies, is underneath.

   The ground is tinted from the artist's newest work (--c1/--c2 are set on the element, with the
   same var(--c1,var(--acc)) fallback every other palette rule uses, so an artist with no works
   still gets a background instead of an invalid one). It bleeds to the screen edge on a phone and
   becomes a card on a desk. */
.artist-head{position:relative;margin:-24px -18px 22px;padding:24px 18px 16px;
  background:
    radial-gradient(120% 150% at 10% 0%,color-mix(in srgb,var(--c1,var(--acc)) 20%,transparent),transparent 60%),
    radial-gradient(100% 130% at 95% 6%,color-mix(in srgb,var(--c2,var(--acc)) 14%,transparent),transparent 58%)}
@media (min-width:640px){.artist-head{margin:0 0 22px;border-radius:var(--r);box-shadow:inset 0 0 0 1px var(--line)}}
@media (min-width:900px){.artist-head{padding:26px 26px 22px}}
/* .artist-ident, not .artist-name: `.artist-name` is already the song page's artist LINK
   (display face, 700, 16px) and, being later in this file at the same specificity, it won — so the
   handle line under the h1 rendered bold and oversized. A second component quietly inherited the
   first one's type. */
.artist-id{display:flex;gap:14px;align-items:center;min-width:0}
.artist-ident{min-width:0}
.artist-ident h1{font-size:clamp(24px,6vw,34px);line-height:1.12;overflow-wrap:anywhere}
.artist-ident .muted{margin-top:2px}
.avatar{border-radius:50%;background:linear-gradient(135deg,var(--acc),var(--acc2));color:var(--ink);display:grid;place-items:center;font:700 22px var(--display)}
/* The ring is drawn from the work's palette, not the brand accent, so it belongs to the ground
   behind it. 68px on a phone: at 84 the name had 190px left beside it and "Dervişler" was the
   longest thing that fitted. */
/* The 3px spacer band is var(--bg) because that is what .artist-head actually sits on — the section
   paints two radial gradients and no background-color, so a --bg2 spacer showed as a mismatched grey
   halo. On the settings page, where this class is also used, --c1 is unset and the ring falls back
   to the accent, which is the intended look there. */
.avatar-lg{width:68px;height:68px;font-size:28px;flex:none;box-shadow:0 0 0 3px var(--bg),0 0 0 5px color-mix(in srgb,var(--c1,var(--acc)) 70%,transparent)}
@media (min-width:560px){.avatar-lg{width:84px;height:84px;font-size:34px}}
.artist-head .bio{margin:14px 0 0;color:var(--text);max-width:62ch}
/* account menu */
.menu{position:relative}
.menu-list{position:absolute;right:0;top:calc(100% + 8px);min-width:190px;background:var(--bg2);border-radius:var(--r-sm);box-shadow:inset 0 0 0 1px var(--line-2),var(--shadow);padding:6px;display:flex;flex-direction:column;z-index:50}
.menu-list a,.menu-list button{display:flex;align-items:center;gap:10px;padding:9px 10px;border-radius:var(--r-ctl);color:var(--text);width:100%;text-align:left;font-weight:500;font-size:14px}
.menu-list a:hover,.menu-list button:hover{background:var(--bg3)}
.menu-list form{display:contents}
.modal{position:relative}
.modal .modal-close{position:absolute;right:16px;top:16px;z-index:1}

/* theme toggle */
.theme-btn{display:inline-flex;align-items:center;justify-content:center}
.theme-btn .on-dark,.theme-btn .on-light{display:none}
[data-theme="light"] .theme-btn .on-light{display:inline-flex}
[data-theme="dark"] .theme-btn .on-dark{display:inline-flex}
:root:not([data-theme]) .theme-btn .on-dark{display:inline-flex}
@media (prefers-color-scheme:light){:root:not([data-theme]) .theme-btn .on-dark{display:none}:root:not([data-theme]) .theme-btn .on-light{display:inline-flex}}

/* ---------- Floating labels (3A-2) ----------
   <div class="fl"><input class="inp" placeholder=" " id="x"><label for="x">Ad</label></div>
   The label starts inside the box and shrinks to the top edge when focused or filled — never disappears. */
.fl{position:relative}
.fl .inp{padding:22px 14px 8px;height:56px}
.fl textarea.inp{height:auto;min-height:96px}
.fl label{position:absolute;left:14px;top:18px;color:var(--dim);font-size:14.5px;pointer-events:none;transition:top .15s var(--ease),font-size .15s var(--ease),color .15s;margin:0;text-transform:none;letter-spacing:0;font-weight:500}
.fl .inp:focus~label,.fl .inp:not(:placeholder-shown)~label{top:7px;font-size:10.5px;letter-spacing:.05em;text-transform:uppercase;color:var(--muted);font-weight:600}
.fl .inp:focus~label{color:var(--acc)}
.fl .inp.invalid~label{color:var(--bad)}
.fl.prefix .inp{padding-left:30px}
.fl.prefix::before{content:"@";position:absolute;left:14px;top:26px;color:var(--dim);font-weight:600;font-size:14.5px;opacity:0;transition:opacity .15s}
.fl.prefix:focus-within::before,.fl.prefix:has(.inp:not(:placeholder-shown))::before{opacity:1}
.fl.prefix label{left:14px}
.fl.prefix .inp:focus~label,.fl.prefix .inp:not(:placeholder-shown)~label{left:14px}
.frow{display:grid;grid-template-columns:1fr 1fr;gap:10px}
@media (max-width:480px){.frow{grid-template-columns:1fr}}
/* utilities (replace one-off inline styles) */
.mt-0{margin-top:0!important}.mt-sm{margin-top:8px}.mt-lg{margin-top:22px}.mb-sm{margin-bottom:10px!important;margin-top:0!important}
.inp-w200{max-width:200px}.row-gap.wrap{flex-wrap:wrap}.tile.empty.empty-sm{min-height:160px}
.h-display.h-page{font-size:clamp(26px,4vw,40px)}
.fstack{display:flex;flex-direction:column;gap:10px;margin-top:6px}
.fstack .field-error{margin-top:-4px}
.date3{display:grid;grid-template-columns:1fr 1.4fr 1.2fr;gap:8px}
.date3 select.inp{appearance:auto;height:48px;padding:10px 12px}
.hdr .avatar-sm{border-radius:var(--r-ctl)}

/* ---------- Light theme character (3A-5) ---------- */
/* The page's two background washes are painted from --atm-a/--atm-b rather than straight from the
   accent, so that ONE rule below can hand them to the cover colour while music plays (see "Cover
   atmosphere"). Registering them as <color> is what lets the swap fade instead of jumping — an
   unregistered custom property cannot be transitioned. */
/* The ambient-light level, written by the player every frame. Registered so it is a real number to
   the engine (and so a calc() built on it never silently drops out on an unset value). */
@property --pulse{syntax:"<number>";inherits:true;initial-value:0}
@property --atm-a{syntax:"<color>";inherits:true;initial-value:transparent}
@property --atm-b{syntax:"<color>";inherits:true;initial-value:transparent}
body.front{--atm-a:var(--acc);--atm-b:var(--acc2);background:
  radial-gradient(1100px 520px at 12% -12%,color-mix(in srgb,var(--atm-a) 14%,transparent),transparent 60%),
  radial-gradient(900px 420px at 100% 0,color-mix(in srgb,var(--atm-b) 10%,transparent),transparent 55%),
  var(--bg);background-attachment:fixed;transition:--atm-a .9s var(--ease),--atm-b .9s var(--ease)}

/* ---------- Cover atmosphere (phones) ----------
   While something is playing, the phone breathes the colour of the record: the page wash, the header
   and the tab bar take the cover's palette. This is deliberately NOT the accent — --acc belongs to the
   brand and is saved in Admin › Settings; writing the cover colour there is what made a saved theme
   colour flash red and revert on the next load. --cov1/--cov2 are set by the player on <html>, exist
   only while the dock is open, and are used for BACKGROUNDS only, so no text or control can lose its
   contrast. Desktop keeps the brand wash: there the player has its own tinted surface already. */
@media (max-width:899.98px){
  /* Phase 16: the cover's colour leads. The ring around the artwork is gone, so the record's presence
     on the screen has to come from the atmosphere instead — a stronger wash, a header and a tab bar
     that carry the colour rather than hint at it. Still backgrounds only: every one of these sits
     behind text that keeps its own token colour, so no reading contrast moves with the cover. */
  body.front.has-atm{--atm-a:var(--cov1,var(--acc));--atm-b:var(--cov2,var(--acc2));background:
    radial-gradient(1200px 620px at 12% -14%,color-mix(in srgb,var(--atm-a) 30%,transparent),transparent 62%),
    radial-gradient(1000px 520px at 100% 2%,color-mix(in srgb,var(--atm-b) 22%,transparent),transparent 58%),
    linear-gradient(180deg,transparent 40%,color-mix(in srgb,var(--atm-a) 12%,transparent)),
    var(--bg)}
  body.has-atm .hdr{background:color-mix(in srgb,var(--cov1,var(--acc)) 17%,var(--glass));transition:background .9s var(--ease)}
  body.has-atm .tabbar{background:color-mix(in srgb,var(--cov1,var(--acc)) 15%,color-mix(in srgb,var(--bg) 92%,transparent));transition:background .9s var(--ease)}
  body.has-atm .dock-bar{background:color-mix(in srgb,var(--c1,var(--acc)) 18%,var(--glass))}
}
@media (prefers-reduced-motion:reduce){body.front,body.has-atm .hdr,body.has-atm .tabbar{transition:none}
  .fbg,.song-hero-bg,.dock-bg{transform:none!important}}
.tile,.card-form,.card-song{box-shadow:inset 0 0 0 1px var(--line),var(--card-shadow)}
.tile:hover{box-shadow:inset 0 0 0 1px var(--line-2),var(--card-shadow)}
.h-display{background:linear-gradient(120deg,var(--text) 0%,var(--text) 55%,color-mix(in srgb,var(--text) 55%,var(--acc)) 100%);-webkit-background-clip:text;background-clip:text;color:transparent}
.h-display em{background:linear-gradient(120deg,var(--acc),var(--acc2));-webkit-background-clip:text;background-clip:text;color:transparent}
.chip{background:var(--bg2)}
.chip.on{color:var(--ink);background:var(--fill);box-shadow:none}
.stat-n{background:linear-gradient(120deg,var(--text),color-mix(in srgb,var(--text) 60%,var(--acc)));-webkit-background-clip:text;background-clip:text;color:transparent}
.hero-row{padding:8px 0 4px}
.ftr{background:var(--bg2)}
.color-field{display:inline-flex;align-items:center;gap:8px}
.color-field input{width:44px;height:32px;border:0;background:none;padding:0;cursor:pointer}

/* ---------- Upload / my works (3C) ---------- */
.up-head{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:18px}
/* The heading only. The button keeps its 44px: `.chip,.btn-accent,.btn-ghost{min-height:44px}` lower
   down is the touch-target floor, it is not inside a media query, and min-height beats height at any
   specificity — so a `height:34px` here was both ineffective and, had it worked, wrong. */
@media (max-width:599px){.up-head{gap:8px;margin-bottom:12px}.up-head .h-page{font-size:22px}.up-head .btn-accent{padding:0 14px;font-size:14px}}
.up-grid{display:grid;gap:22px;grid-template-columns:1fr}
@media (min-width:960px){.up-grid{grid-template-columns:340px 1fr;align-items:start}}
.up-files{display:flex;flex-direction:column;gap:10px;position:sticky;top:84px}
@media (max-width:959.98px){.up-files{position:static}}
.drop{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;text-align:center;padding:22px 16px;border:1.5px dashed var(--line-2);border-radius:var(--r);background:var(--bg2);color:var(--muted);cursor:pointer;transition:border-color .2s,background .2s;min-height:120px}
/* The file inputs are sr-only now rather than hidden (they had to be, or no keyboard could reach
   them), so the focus ring has to be drawn on the label the user can actually see. */
.drop:focus-within{border-color:var(--acc);box-shadow:0 0 0 2px color-mix(in srgb,var(--acc) 55%,transparent)}
.avatar-pick:focus-within{box-shadow:inset 0 0 0 1px var(--acc),0 0 0 2px color-mix(in srgb,var(--acc) 45%,transparent)}
.drop:hover,.drop.over{border-color:var(--acc);background:color-mix(in srgb,var(--acc) 6%,var(--bg2))}
.drop.invalid{border-color:var(--bad)}
.drop.has-file{border-style:solid;border-color:color-mix(in srgb,var(--acc) 60%,transparent)}
/* While the browser is re-encoding the picked MP3 (assets/js/mp3.js) the zone is not a drop target:
   a second file dropped mid-encode would race the first one into the input.

   The wait is real — ten to thirty seconds on a phone — so it gets a ring that fills rather than a line
   of text counting percentages at someone. --p (0..1) is set by app.js; conic-gradient turns it into a
   dial with no SVG and no extra element. */
.drop.busy{pointer-events:none}
.drop.busy .drop-ic{position:relative;display:grid;place-items:center;width:52px;height:52px;border-radius:50%;
  background:conic-gradient(var(--acc) calc(var(--p,0) * 360deg),color-mix(in srgb,var(--acc) 16%,transparent) 0);
  transition:background .25s linear}
.drop.busy .drop-ic::after{content:"";position:absolute;inset:5px;border-radius:50%;background:var(--bg2)}
.drop.busy .drop-ic svg{position:relative;z-index:1;width:20px;height:20px;opacity:.85;animation:dropbeat 1.4s ease-in-out infinite}
.drop.busy::after{content:"";position:absolute;left:0;right:0;bottom:0;height:3px;border-radius:0 0 var(--r) var(--r);
  background:linear-gradient(90deg,var(--c1,var(--acc)),var(--acc));transform-origin:left;transform:scaleX(var(--p,0));transition:transform .25s linear}
@keyframes dropbeat{0%,100%{transform:scale(1);opacity:.85}50%{transform:scale(1.12);opacity:1}}
.drop.done{border-style:solid;border-color:color-mix(in srgb,var(--ok) 55%,transparent)}
.drop.done [data-drop-sub]{color:var(--text)}
/* A one-line notice that opens into the honest detail only for whoever wants it — an upload form
   is the wrong place for a paragraph of warning, and the wrong place to say nothing at all. */
.hint-more{cursor:default}
.hint-more>summary{cursor:pointer;list-style:none;display:inline-flex;align-items:center;gap:6px}
.hint-more>summary::-webkit-details-marker{display:none}
.hint-more>summary::after{content:"";width:5px;height:5px;border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;transform:rotate(45deg) translateY(-1px);transition:transform .18s var(--ease);opacity:.7}
.hint-more[open]>summary::after{transform:rotate(-135deg) translateY(-1px)}
.hint-more[open]{padding-bottom:2px}

@media (prefers-reduced-motion:reduce){.drop.busy .drop-ic svg{animation:none}}
.drop b{color:var(--text);font-family:var(--display);font-weight:600}
.drop-ic{width:46px;height:46px;border-radius:var(--r-sm);background:color-mix(in srgb,var(--acc) 16%,transparent);color:var(--acc);display:grid;place-items:center}
.drop-cover{position:relative;aspect-ratio:1;padding:0;overflow:hidden}
.drop-cover img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.drop-cover .drop-in{position:relative;display:flex;flex-direction:column;align-items:center;gap:6px;padding:16px;border-radius:var(--r-sm);z-index:1}
.drop-cover.has-file .drop-in{background:rgba(15,16,19,.62);color:#fff;backdrop-filter:blur(4px)}
.drop-cover.has-file b{color:#fff}
.fl-static{top:7px!important;font-size:10.5px!important;letter-spacing:.05em!important;text-transform:uppercase!important;color:var(--muted);font-weight:600!important}
.fl select.inp{appearance:auto;padding:22px 14px 8px;height:56px}
.progress{position:relative;height:36px;border-radius:var(--r-ctl);background:var(--bg3);overflow:hidden;display:flex;align-items:center;padding:0 12px;font-size:13px;color:var(--muted)}
.progress i{position:absolute;left:0;top:0;bottom:0;width:0;background:color-mix(in srgb,var(--acc) 35%,transparent);transition:width .2s}
.progress span{position:relative}
.up-actions{display:flex;gap:10px;justify-content:flex-end;flex-wrap:wrap;margin-top:6px}
.works{display:grid;gap:10px}
/* Three children, two columns below 760px: the action row lands on a second row and MUST be told to
   span both. Without the span, grid auto-placement puts it in column 1 — the 72px cover column — and
   every button inside, being wider than that, wraps onto a line of its own. Measured before the fix:
   the action row 72px wide and 178px tall, the card 294px for a single song on a 400px screen. */
.work{display:grid;grid-template-columns:72px 1fr;gap:10px 14px;align-items:start;padding:12px;border-radius:var(--r);background:var(--bg2);box-shadow:inset 0 0 0 1px var(--line),var(--card-shadow)}
/* The span, in the BASE rule and not in a phone-only block: the card is two columns at every width
   below the one where a third genuinely fits, and the action row has to be told to cross both of
   them at every one of those widths. */
.work-actions{grid-column:1/-1}
/* 940px, not 760px. At 760 the third column is `auto` — sized to the action row's max-content, which
   for a work with lyrics awaiting review ("İncelemeye gönder" + five other controls) is 572px. That
   left 28px for the title column, and the card that was being made SHORTER rendered 268px tall with
   the title broken one word per line. Measured at 320/400/760/1280. 940 is the first width at which
   the widest possible row and a readable title both fit. */
@media (min-width:940px){
  .work{grid-template-columns:72px 1fr auto;gap:14px;align-items:center}
  .work-actions{grid-column:auto}
}
.work-cover{width:72px;height:72px;border-radius:var(--r-sm);background:var(--bg3) center/cover}
.work-meta{display:flex;flex-direction:column;gap:3px;min-width:0}
.work-meta b{display:block;min-width:0;font-family:var(--display);font-weight:600;font-size:16px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.work-meta .small{font-size:12.5px}
.work-actions{display:flex;gap:6px;align-items:center;flex-wrap:wrap}
.work-actions form{display:contents}
/* The destructive control keeps its distance from the ones tapped all day. A margin, not a spacer
   element: `form{display:contents}` above means the BUTTON is the flex item, so the auto margin goes
   on the button. The first attempt used two empty <span class="act-gap"> with flex:1, which did
   nothing at all on a desk — the card's third grid column is `auto`, sized to max-content, so there
   is never any free space for a flex spacer to claim — while on a phone the two spans still cost
   16px of gap each in a row that was already over budget. */
.work-actions > form:last-of-type > .danger{margin-inline-start:auto}
@media (max-width:759.98px){
  .work{grid-template-columns:64px 1fr}
  .work-cover{width:64px;height:64px}
  .work-actions{flex-wrap:nowrap;gap:8px}
  /* Icons only: five controls plus a labelled "Yayınla" still fit on one 340px line this way, and a
     sixth would shrink rather than wrap the card open again. */
  .work-actions .lbl{display:none}
  .work-actions .btn-ghost.sm{padding-left:9px;padding-right:9px}
  /* "İncelemeye gönder" is the longest label the row can carry, and at 320px — 260px of usable row
     after the page and card padding — five icons plus that label does not fit. min-width:0 has to be
     on the SPAN as well as the button: the span is itself a flex item, and without it its floor is
     the width of its longest word, which pushed the delete button off the clipped edge where nothing
     could scroll to it. overflow-x is the belt to that braces: if a future control breaks the budget
     again the row scrolls instead of hiding something. */
  .work-actions{overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none}
  .work-actions::-webkit-scrollbar{display:none}
  .work-actions .btn-accent.sm{white-space:nowrap;min-width:0}
  .work-actions .btn-accent.sm span{min-width:0;overflow:hidden;text-overflow:ellipsis}
}
/* The state vocabulary, identical to admin.css: one stripe down the left edge instead of a pill that
   costs a whole line to repeat what the colour already says. Five words, four colours — `idle` is the
   panel's only extra and the front has nothing that means it. The default rule matters: a state the
   sheet does not know paints a neutral stripe rather than silently nothing. */
/* The state vocabulary, the same five words the panel uses: one stripe down the left edge instead of
   a pill that costs a whole line to repeat what the colour already says.

   Drawn as a pseudo-element, NOT as admin.css's `inset 3px 0 0` shadow. An inset shadow follows the
   border-radius, and the two sheets do not share one: admin's --r is 8px, the front's is 20px. On a
   96px-tall card that is 40px of curve on a 96px stripe — over 40% of it bending away from the edge,
   reading as two commas rather than a bar. Inset from the corners instead, with its own small radius.

   position:relative on the card, because .work is a grid: an in-flow pseudo-element would become a
   grid ITEM and take a cell of its own. The default rule matters too — a state the sheet does not
   know paints a neutral stripe rather than nothing at all. */
.state-card{position:relative}
.state-card[data-state]::before{content:"";position:absolute;left:0;top:14px;bottom:14px;width:3px;border-radius:0 3px 3px 0;background:var(--line-2)}
.state-card[data-state="live"]::before{background:var(--ok)}
.state-card[data-state="wait"]::before{background:var(--warn)}
.state-card[data-state="bad"]::before{background:var(--bad)}
.state-card[data-state="off"]::before{background:var(--dim)}
.btn-ghost.danger:hover{color:var(--bad)!important;box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--bad) 50%,transparent)}

/* ---------- Song grid, feature, song page (3D) ---------- */
.section{margin-top:26px}
.section-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin-bottom:12px}
.eyebrow{display:inline-flex;align-items:center;gap:6px;font-size:11.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);font-weight:600}
.song-grid{display:grid;gap:14px;grid-template-columns:repeat(2,1fr)}
@media (min-width:640px){.song-grid{grid-template-columns:repeat(3,1fr)}}
@media (min-width:900px){.song-grid{grid-template-columns:repeat(4,1fr)}}
@media (min-width:1200px){.song-grid{grid-template-columns:repeat(5,1fr)}}
/* Few items: stop stretching. Three cards spread across five columns become three billboards with a
   gap where the catalogue should be — the same three at their natural width read as a small, tidy
   collection. data-count is written by the template; auto-fill then lays out what is actually there. */
@media (min-width:640px){
  .song-grid[data-count="1"],.song-grid[data-count="2"],.song-grid[data-count="3"]{grid-template-columns:repeat(auto-fill,minmax(0,232px))}
  .artist-grid[data-count="1"],.artist-grid[data-count="2"]{grid-template-columns:repeat(auto-fill,minmax(0,232px))}
}
.card-song .art{display:block;aspect-ratio:1;background:var(--bg3) center/cover;transition:transform .5s var(--ease)}
.card-song .art img,.song-cover img{display:block;width:100%;height:100%;object-fit:cover}
.song-cover img{position:absolute;inset:0}
/* A success flash is news, not an instruction: it says the thing you just did worked, and then it is
   in the way. As a block in the flow it was ~70px of full-width panel that pushed the whole page down
   and stayed there until the next navigation. So: float it and let app.js retire it.

   [data-flash], not .notice-success. The class is written by front/report/copyright.php (a legal
   receipt with a three-working-day deadline in it) and by front/playlist/_picker.php (inside a modal)
   as well, and both are permanent messages. Floating those was wrong in two different ways: the
   receipt deleted itself after five seconds, and the picker's note escaped its own modal to sit at
   the bottom of the viewport with nothing able to dismiss it.

   Only under :root.js. theme.js sets that class, blocking, in <head>, so the page is painted as a
   toast from the very first frame — no block that jumps out of the flow a moment later. Without
   JavaScript the class never appears, the rule never applies, and the message stays exactly where it
   was: in the flow, permanent, readable. That is the right fallback, because a toast nobody can
   dismiss and nothing can time out is worse than a block.

   Errors and warnings are deliberately NOT included. They carry something the reader has to act on
   ("e-postanı doğrula"), and a message that disappears on a timer is the wrong shape for that. */
:root.js .notice-success[data-flash]{position:fixed;left:50%;bottom:calc(var(--tab-total) + 12px);transform:translateX(-50%);z-index:64;width:max-content;max-width:min(92vw,520px);margin:0;padding:11px 14px;border-radius:999px;background:var(--bg2);box-shadow:var(--shadow),inset 0 0 0 1px color-mix(in srgb,var(--ok) 50%,transparent);font-size:14px;display:flex;align-items:center;gap:10px;pointer-events:auto;animation:toast-in .22s var(--ease) both}
:root.js .notice-success[data-flash].is-going{animation:toast-out .3s var(--ease) both}
:root.js .notice-success[data-flash] .toast-x{flex:none;display:grid;place-items:center;width:24px;height:24px;margin:-4px -6px -4px 0;border-radius:50%;color:var(--muted)}
:root.js .notice-success[data-flash] .toast-x:hover{color:var(--text);background:var(--bg3)}
/* Above the player when there is one, above the tab bar when there is not, and clear of both on a
   desk where the tab bar does not exist. Two things had to be got right here and one of them was not:
   the first version anchored under the HEADER, where a fixed element lands squarely on the page
   heading; and the has-dock override was written as `body.has-dock .notice-success`, which is
   (0,2,1) against the (0,3,0) of the :root.js rule — so it never applied and the toast sat inside the
   player. Both overrides now carry :root.js themselves. */
:root.js body.has-dock .notice-success[data-flash]{bottom:calc(var(--dock-h) + var(--tab-total) + 12px)}
@media (min-width:900px){
  :root.js .notice-success[data-flash]{bottom:12px}
  :root.js body.has-dock .notice-success[data-flash]{bottom:calc(var(--dock-h) + 12px)}
}
/* Three things want the same corner. The install banner is full-width and on top, so the toast steps
   over it; a service-worker toast is the same shape, so it steps over the flash. Rare combinations,
   but each one silently hides a message if left alone. */
:root.js body:has(.install-banner) .notice-success[data-flash]{bottom:calc(var(--dock-h) + var(--tab-total) + 84px)}
body:has(.notice-success[data-flash]) .sw-toast{bottom:calc(var(--dock-h) + var(--tab-total) + 66px)}
@media (min-width:900px){body:has(.notice-success[data-flash]) .sw-toast{bottom:calc(var(--dock-h) + 66px)}}
@keyframes toast-in{from{opacity:0;transform:translate(-50%,14px)}to{opacity:1;transform:translate(-50%,0)}}
@keyframes toast-out{from{opacity:1;transform:translate(-50%,0)}to{opacity:0;transform:translate(-50%,14px)}}
@media (prefers-reduced-motion:reduce){:root.js .notice-success[data-flash],:root.js .notice-success[data-flash].is-going{animation:none}}
.sw-toast{position:fixed;left:50%;bottom:calc(var(--dock-h) + var(--tab-total) + 12px);transform:translateX(-50%);z-index:60;display:flex;gap:10px;align-items:center;padding:10px 12px 10px 16px;border-radius:999px;background:var(--bg2);color:var(--text);box-shadow:var(--shadow),inset 0 0 0 1px var(--line);font-size:14px}
@media (min-width:900px){.sw-toast{bottom:calc(var(--dock-h) + 12px)}}
.lyrics-title{font-size:15px;font-weight:600;color:var(--muted);margin:0 0 10px;letter-spacing:0}
.card-song .meta a{color:inherit}
.card-song .meta span a:hover{color:var(--text)}
.card-song.playing{box-shadow:inset 0 0 0 1.5px var(--c1,var(--acc)),var(--card-shadow)}
/* feature (haftanın eseri) */
.feature{display:grid;grid-template-columns:1fr;border-radius:var(--r-lg);overflow:hidden;background:
  radial-gradient(700px 300px at 0% 0%,color-mix(in srgb,var(--c1,var(--acc)) 35%,transparent),transparent 60%),
  radial-gradient(600px 300px at 100% 100%,color-mix(in srgb,var(--c2,var(--acc)) 30%,transparent),transparent 60%),var(--bg2);box-shadow:inset 0 0 0 1px var(--line),var(--card-shadow);margin-bottom:8px}
@media (min-width:760px){.feature{grid-template-columns:300px 1fr}}
.feature-art{aspect-ratio:1;background:var(--bg3) center/cover;min-height:240px}
.feature-body{padding:22px 24px;display:flex;flex-direction:column;gap:8px;justify-content:center}
.feature-body h2{font-size:clamp(24px,3.4vw,38px);font-weight:700;letter-spacing:-.02em}
.feature-body h2 a{color:var(--text)}
.feature-story{color:var(--muted);max-width:60ch;margin:2px 0 0}
.feature-actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:8px}
/* song page */
.song-page{--pal-glow:color-mix(in srgb,var(--c1) 22%,transparent)}
/* -24px cancels .page's top padding: the hero is a full-bleed panel and belongs directly under the
   header, not floating below a strip of page background. */
.song-hero{position:relative;margin:-24px -18px 0;padding:24px 18px 26px;overflow:hidden;isolation:isolate;border-radius:0 0 var(--r-lg) var(--r-lg)}
@media (min-width:900px){.song-hero{margin:-8px -28px 0;padding:32px 28px 30px}}
.song-hero-bg{position:absolute;inset:-40px;z-index:-2;background:center/cover;filter:blur(48px) saturate(1.45);opacity:.52;transform:scale(1.18)}
/* body.song-live is set by the player only while THIS page's work is the one playing. */
body.song-live .song-hero-bg{opacity:calc(.5 + var(--pulse,0) * .34);transform:scale(calc(1.18 + var(--pulse,0) * .07));will-change:opacity,transform}
.song-hero::after{content:"";position:absolute;inset:0;z-index:-1;background:linear-gradient(180deg,color-mix(in srgb,var(--bg) 35%,transparent),var(--bg) 92%)}
.song-hero-in{max-width:var(--max);margin:0 auto;display:grid;grid-template-columns:1fr;gap:20px;align-items:end}
@media (min-width:760px){.song-hero-in{grid-template-columns:260px 1fr;gap:28px}}
.song-cover{position:relative;aspect-ratio:1;width:min(260px,70vw);border-radius:var(--r);background:var(--bg3) center/cover;box-shadow:0 30px 60px -24px rgba(0,0,0,.6),inset 0 0 0 1px rgba(255,255,255,.12);overflow:hidden}
.song-info{display:flex;flex-direction:column;gap:10px;min-width:0}
.song-info h1{font-size:clamp(30px,5vw,56px);font-weight:800;letter-spacing:-.03em;line-height:1}
.song-by{margin:0;color:var(--muted);font-size:15px}
.song-by a{color:var(--text);font-weight:600}
/* No longer rendered on the work page (see the note in front/song/show.php); kept because the
   admin work list and the tag index still print chip rows under this class. */
.song-tags{display:flex;gap:6px;flex-wrap:wrap}
.song-body{display:grid;grid-template-columns:1fr;gap:28px;margin-top:26px}
@media (min-width:960px){.song-body{grid-template-columns:1fr 320px}}
.song-main{min-width:0}
.lyrics{font-family:var(--display);font-size:clamp(19px,2.2vw,24px);line-height:1.45;font-weight:500;color:var(--muted);max-width:60ch}
.lyrics p{margin:0 0 8px;transition:color .3s,transform .3s;scroll-margin:40vh}
.lyrics p.gap{height:14px;margin:0}
.lyrics[data-timed] p{color:var(--dim)}
.lyrics p.now{color:var(--text);font-weight:600}
.lyrics p.past{color:var(--muted)}
.lyrics[data-timed] p.now::before{content:"";display:inline-block;width:7px;height:7px;border-radius:50%;background:var(--c1,var(--acc));margin:0 10px 4px 0}
.lyrics:not([data-timed]) p{color:var(--text)}
.story-block{margin-top:28px}
.story{color:var(--muted);font-size:16px;line-height:1.7;max-width:62ch}
.artist-card{display:flex;gap:14px;align-items:flex-start;padding:16px;border-radius:var(--r);background:var(--bg2);box-shadow:inset 0 0 0 1px var(--line),var(--card-shadow);margin-bottom:18px}
.artist-card .avatar-lg{width:56px;height:56px;font-size:22px;flex:none;box-shadow:none}
.artist-name{font-family:var(--display);font-weight:700;font-size:16px}
.small{font-size:13px}
.song-list{display:flex;flex-direction:column;gap:8px}
.song-row{display:flex;gap:10px;align-items:center;padding:8px;border-radius:var(--r-sm);background:var(--bg2);box-shadow:inset 0 0 0 1px var(--line)}
.song-row .th{width:44px;height:44px;border-radius:var(--r-ctl);background:var(--bg3) center/cover;flex:none}
.song-row .min{flex:1;min-width:0;display:flex;flex-direction:column}
/* The truncation goes on the <a>, because the <a> is the flex item and the <b> inside it is an
   ordinary inline box — and `overflow`/`text-overflow` do nothing to an inline box, while the
   `white-space:nowrap` in the same rule very much does: it made the row's min-content width the full
   untruncated title. Measured at 360px with one realistic Turkish title: the document 560px wide
   inside a 360px screen, and body{overflow-x:hidden} so the missing 200px could not be scrolled to.
   Off-screen and unreachable on the song page: the button that posts a comment, every row's play
   button, every report flag. min-width:0 on .song-side for the same reason one level up. */
.song-side{min-width:0}
.song-row .min > a{display:block;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.song-row .min b{font-weight:600}
.song-row .p{width:34px;height:34px;border-radius:var(--r-ctl);background:var(--bg3);display:grid;place-items:center;flex:none}
.artist-links{display:flex;gap:6px;flex-wrap:wrap;margin-top:12px}
/* One bar: the numbers, then what you can do — the actions at the far end on a desk, under the
   numbers on a phone. As two separate wrapping flex children they each took a line of their own,
   which is how "Eserlerim" ended up alone on a row. */
.artist-bar{display:flex;flex-wrap:wrap;gap:10px 14px;align-items:center;margin-top:16px}
.artist-cta{display:flex;gap:8px;flex-wrap:wrap;align-items:center}
@media (min-width:560px){.artist-cta{margin-inline-start:auto}}
.pager{display:flex;gap:4px;flex-wrap:wrap;margin-top:18px}
/* A genuine 44px box. The old 30px link with a 44px invisible ::after pad meant adjacent pads
   overlapped by 11px on a 4px gap — aiming at page 12 landed on 13. The pad is removed below. */
.pager a{min-width:44px;height:44px;padding:0 10px;display:grid;place-items:center;border-radius:var(--r-ctl);color:var(--muted);font-variant-numeric:tabular-nums}
.pager .gap{display:grid;place-items:center;min-width:24px;height:44px;color:var(--dim)}
.pager a.on{background:var(--bg3);color:var(--text)}

/* ---------- Lyrics timing tool (3E) ---------- */
.timing-bar{position:sticky;top:72px;z-index:5;display:flex;gap:8px;align-items:center;flex-wrap:wrap;padding:10px;border-radius:var(--r);background:var(--glass-2);backdrop-filter:blur(14px);box-shadow:inset 0 0 0 1px var(--line-2),var(--shadow);margin-bottom:14px}
.timing-clock{font-family:var(--display);font-variant-numeric:tabular-nums;font-size:22px;font-weight:600;min-width:84px;text-align:center}
.timing-auto{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin:0 0 14px;padding:10px 12px;border-radius:var(--r);background:var(--bg2);box-shadow:inset 0 0 0 1px var(--line)}
.timing-auto .small{max-width:62ch;line-height:1.5}
.timing-bar kbd{font:11px ui-monospace,monospace;padding:1px 5px;border-radius:4px;background:rgba(0,0,0,.2);margin-left:4px}
.timing-lines{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:4px}
.timing-lines li{display:grid;grid-template-columns:64px 1fr auto;gap:10px;align-items:center;padding:8px 10px;border-radius:var(--r-ctl);background:var(--bg2);box-shadow:inset 0 0 0 1px var(--line);cursor:pointer;font-family:var(--display);font-size:17px}
.timing-lines li.gap{opacity:.45;font-size:13px}
.timing-lines li.gap button{visibility:hidden}
.timing-lines li .t{font-family:var(--body);font-variant-numeric:tabular-nums;font-size:13px;color:var(--muted)}
.timing-lines li.set .t{color:var(--acc);font-weight:600}
.timing-lines li.now{box-shadow:inset 0 0 0 1.5px var(--acc);background:color-mix(in srgb,var(--acc) 8%,var(--bg2))}
.timing-lines li.next{box-shadow:inset 0 0 0 1px var(--line-2)}
.work-thumb{width:48px;height:48px;border-radius:var(--r-ctl);background:var(--bg3) center/cover;flex:none}

/* ---------- Likes, follows, library (4A) ---------- */
.card-song .like{position:absolute;left:10px;top:10px;display:inline-flex;align-items:center;gap:4px;padding:5px 8px;border-radius:var(--r-ctl);background:rgba(15,16,19,.55);color:#fff;font-size:12px;backdrop-filter:blur(6px);opacity:0;transition:opacity .2s,transform .2s var(--ease)}
.card-song:hover .like,.card-song .like.on,.card-song .like:focus-visible{opacity:1}
@media (hover:none){.card-song .like{opacity:1}}
.card-song .like.on{color:#ff6b81}
.card-song .like:active{transform:scale(.92)}
.like-btn.on{color:#ff6b81!important;box-shadow:inset 0 0 0 1px color-mix(in srgb,#ff6b81 55%,transparent)}
.follow-btn.on{background:var(--bg3)!important;color:var(--text)!important;box-shadow:inset 0 0 0 1px var(--line-2)}
.artist-stats{color:var(--muted);font-size:14px}
.artist-stats b{color:var(--text)}
/* Young-catalogue invitation (front/home/_invite.php) — takes the place the empty panels took. */
.invite{display:flex;gap:18px;align-items:flex-start;padding:22px;border-radius:var(--r-lg);background:
  radial-gradient(120% 140% at 0% 0%,color-mix(in srgb,var(--acc) 12%,transparent),transparent 70%),var(--bg2);
  box-shadow:inset 0 0 0 1px var(--line),var(--card-shadow)}
.invite-mark{flex:none;width:52px;height:52px;border-radius:16px;display:grid;place-items:center;background:color-mix(in srgb,var(--acc) 18%,transparent);color:var(--acc)}
.invite-mark svg{width:26px;height:26px}
.invite-body{min-width:0}
.invite-body h2{margin:2px 0 6px}
.invite-body p{margin:0;max-width:64ch;line-height:1.6}
.invite-actions{display:flex;gap:10px;flex-wrap:wrap;margin-top:14px}
@media (max-width:559.98px){.invite{flex-direction:column;gap:14px;padding:18px}}
.artist-grid{display:grid;gap:12px;grid-template-columns:repeat(2,1fr)}
@media (min-width:760px){.artist-grid{grid-template-columns:repeat(4,1fr)}}
.artist-tile{display:flex;flex-direction:column;gap:6px;padding:12px;border-radius:var(--r);background:var(--bg2);box-shadow:inset 0 0 0 1px var(--line),var(--card-shadow);transition:transform .2s var(--ease)}
.artist-tile:hover{transform:translateY(-2px)}
.artist-tile-art{aspect-ratio:1;border-radius:var(--r-sm);background:linear-gradient(135deg,var(--acc),var(--acc2)) center/cover;display:grid;place-items:center;color:var(--ink);font:700 34px var(--display)}
.artist-tile b{font-family:var(--display);font-weight:600}
.pop{animation:pop .3s var(--ease)}
@keyframes pop{50%{transform:scale(1.25)}}

/* ===== Comments & covers (4B) ===== */
.comments-block{margin-top:28px}
.comments-block .section-head h2,.song-side .section-head h2{display:flex;align-items:center;gap:8px}
.comment-list{display:flex;flex-direction:column;gap:14px;margin-bottom:18px}
.comment{display:flex;gap:12px;align-items:flex-start}
.comment .avatar-sm{flex:0 0 auto;width:36px;height:36px;font-size:14px;cursor:default;text-decoration:none}
.comment-body{flex:1;min-width:0;background:var(--bg2);box-shadow:inset 0 0 0 1px var(--line);border-radius:var(--r-sm);padding:10px 14px}
.comment-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:4px}
.comment-author{font-weight:600;color:var(--text);text-decoration:none}
.comment-author:hover{color:var(--acc)}
.comment-head time{margin-left:auto}
.comment-remove{display:inline-flex;margin:0}
.comment-text{margin:0;white-space:pre-wrap;word-wrap:break-word;line-height:1.55}
.comment.pop{animation:fadeup .35s var(--ease)}
@keyframes fadeup{from{opacity:0;transform:translateY(6px)}}
.chip-xs{padding:2px 7px;font-size:11px;gap:4px}
.icon-btn{display:inline-grid;place-items:center;border:0;background:transparent;color:var(--muted);border-radius:var(--r-ctl);cursor:pointer;padding:4px}
.icon-btn:hover{color:var(--text);background:var(--bg3,var(--bg2))}
.icon-btn.xs{width:26px;height:26px}
.comment-form{display:flex;gap:12px;align-items:flex-start}
.comment-form .avatar-sm{flex:0 0 auto;width:36px;height:36px;font-size:14px;cursor:default}
.comment-form-in{flex:1;min-width:0}
.comment-form textarea.inp{width:100%;resize:vertical;min-height:64px}
.comment-form-foot{display:flex;justify-content:space-between;align-items:center;margin-top:8px}
.comment-gate{padding:12px 14px;background:var(--bg2);border-radius:var(--r-sm);box-shadow:inset 0 0 0 1px var(--line)}
/* ----- the comment sheet (Phase 16): the same block inside the modal, which is already a bottom
   sheet on phones. The list scrolls, the form stays put at the bottom where a thumb can reach it. */
.comments-block.in-sheet{margin:0;display:flex;flex-direction:column;min-height:0}
.comment-more{margin:0 0 18px}
.comment-pager{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:8px 14px;margin-bottom:18px}
.comment-pager .pager{margin-top:0}
.comments-block.in-sheet .section-head{margin-bottom:2px;padding-right:44px}
.comments-block.in-sheet .sheet-sub{margin:0 0 14px}
.comments-block.in-sheet .sheet-sub a{color:var(--muted)}
.comments-block.in-sheet .comment-list{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;margin:0 -4px 14px;padding:0 4px}
.comments-block.in-sheet .comment-form{position:sticky;bottom:0;background:var(--bg2);padding-top:10px}
/* max-height, not height: an empty comment sheet should hug its content instead of opening as a
   half-screen of nothing. A long thread grows to the cap and scrolls inside the list. */
.modal:has(.comments-block.in-sheet){max-width:560px;max-height:min(82vh,820px);display:flex;flex-direction:column;padding-bottom:calc(20px + env(safe-area-inset-bottom))}
/* ----- the search sheet (Phase 16) ----- */
/* The search sheet is the one modal that opens at the TOP of a phone screen. Everything else here
   is a bottom sheet, which is right when a thumb is the input device — but this one focuses a field
   the moment it opens, the keyboard immediately claims the lower half of the screen, and a bottom
   sheet ends up either behind it or crushed into the strip above it, with the suggestions nowhere.
   Docked to the top, the field lands where the eye already is and every pixel between it and the
   keyboard belongs to the results. Above 640px the modal is centred and none of this applies. */
@media (max-width:639.98px){
  #modal-root:has(.sheet-search){place-items:start center}
  .modal:has(.sheet-search){
    border-radius:0 0 var(--r-lg) var(--r-lg);
    animation:sheet-down .28s var(--ease);
    padding:calc(16px + env(safe-area-inset-top)) 18px 20px;
    max-height:100%}
  .modal:has(.sheet-search) .modal-close{top:calc(16px + env(safe-area-inset-top))}
}
@keyframes sheet-down{from{transform:translateY(-40px);opacity:0}}
.sheet-search{margin-bottom:6px;padding:13px 16px;border-radius:var(--r)}
.sheet-search input{font-size:17px}          /* never below 16px, or iOS zooms the whole page on focus */
/* In the sheet the suggestions are part of the flow, not an overlay: the modal scrolls its own
   content, so an absolutely positioned dropdown would be clipped by it. */
/* The suggestion list is taken out of absolute positioning so it can sit in the flow — but the flow
   of .search is a nowrap flex ROW, so it became a third item beside the icon and the field and took
   the width: measured, the input at 4.0px and the typed text invisible, on the primary way to search
   on a phone. It needs a line of its own. */
.sheet-search{flex-wrap:wrap}
.sheet-search .suggest{flex:1 0 100%;position:static;margin-top:12px;box-shadow:inset 0 0 0 1px var(--line);max-height:46vh;overflow-y:auto}
.sheet-group{margin-top:18px}
.sheet-group .eyebrow{display:block;margin-bottom:10px}
.sheet-group .chip-row{display:flex;flex-wrap:wrap;gap:8px}
/* Bottom inset only matters while the sheet is anchored to the bottom — above 640px it is centred,
   and below it the block above has moved it to the top. */
@media (min-width:640px){.modal:has(.sheet-search){padding-bottom:24px}}
.song-cover-of{display:flex;align-items:center;gap:6px;margin:-6px 0 12px;font-size:14px;color:var(--muted);flex-wrap:wrap}
.song-cover-of a{color:var(--text)}
.cover-of{padding:12px 14px;background:var(--bg2);border-radius:var(--r-sm);box-shadow:inset 0 0 0 1px var(--line)}
.cover-of.invalid{box-shadow:inset 0 0 0 1px var(--bad)}
.cover-of-body{margin-top:10px;display:flex;flex-direction:column;gap:8px}
.cover-of-picked{display:flex;align-items:center;gap:8px;padding:8px 12px;border-radius:var(--r-ctl);background:var(--bg);box-shadow:inset 0 0 0 1px var(--acc);font-weight:600}
.cover-of-picked span{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cover-of-search{position:relative}
.cover-of-results{position:absolute;left:0;right:0;top:calc(100% + 4px);z-index:20;background:var(--bg2);border-radius:var(--r-sm);box-shadow:0 12px 32px rgba(0,0,0,.25),inset 0 0 0 1px var(--line);overflow:hidden;max-height:300px;overflow-y:auto}
.cover-of-results button{display:flex;width:100%;gap:10px;align-items:center;padding:8px 12px;border:0;background:transparent;color:var(--text);text-align:left;cursor:pointer;font:inherit}
.cover-of-results button:hover,.cover-of-results button:focus{background:var(--bg3,var(--bg))}
.cover-of-results .th{width:36px;height:36px;border-radius:4px;background:center/cover var(--bg);flex:0 0 auto}
.cover-of-results small{display:block;color:var(--muted)}
.cover-of-results .none{padding:10px 12px;color:var(--muted);font-size:13px}

/* ===== Reports & copyright (4C) ===== */
.btn-ghost.icon-only{padding-left:12px;padding-right:12px}
.report-target{display:flex;gap:6px;align-items:center;margin:-6px 0 14px;font-size:14px}
.radios-col label{align-items:flex-start}
.radios-col input{margin-top:3px}
.report-done{display:flex;gap:10px;align-items:flex-start;padding:14px;border-radius:var(--r-sm);background:color-mix(in srgb,var(--ok,#3aa76d) 12%,var(--bg2));color:var(--text);margin:0}
.report-done .i{color:var(--ok,#3aa76d);flex:0 0 auto}
.copyright-page{max-width:72ch}
.copyright-form{margin-top:20px;padding:20px;border-radius:var(--r);background:var(--bg2);box-shadow:inset 0 0 0 1px var(--line)}

/* ===== Search (5A) ===== */
.search{position:relative}
.suggest{position:absolute;left:0;right:0;top:calc(100% + 6px);z-index:60;background:var(--bg2);border-radius:var(--r-sm);box-shadow:0 18px 40px rgba(0,0,0,.35),inset 0 0 0 1px var(--line);overflow:hidden;text-align:left}
.suggest .sg-group{padding:8px 12px 4px;font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}
.suggest a{display:flex;gap:10px;align-items:center;padding:8px 12px;color:var(--text);text-decoration:none}
.suggest a:hover,.suggest a.on{background:color-mix(in srgb,var(--acc) 12%,var(--bg2))}
.suggest .th{width:34px;height:34px;border-radius:4px;background:center/cover var(--bg3);flex:0 0 auto;display:grid;place-items:center;font-weight:700;font-family:var(--display)}
.suggest b{display:block;font-weight:600;line-height:1.2}
.suggest small{color:var(--muted)}
.suggest .sg-all{justify-content:center;color:var(--acc);font-weight:600;border-top:1px solid var(--line)}
.search-page{max-width:980px}
.search-big{display:flex;align-items:center;gap:10px;padding:6px 6px 6px 16px;background:var(--bg2);border-radius:var(--r);box-shadow:inset 0 0 0 1px var(--line);margin-bottom:22px}
.search-big input{flex:1;background:transparent;border:0;color:var(--text);font-size:17px;padding:10px 0;outline:0;min-width:0}
.search-big .btn-accent{padding:10px 18px}
.tabs{display:flex;gap:4px;margin:0 0 18px;border-bottom:1px solid var(--line)}
.tabs a{padding:10px 14px;color:var(--muted);text-decoration:none;font-weight:600;border-bottom:2px solid transparent;margin-bottom:-1px}
.tabs a[aria-current]{color:var(--text);border-color:var(--acc)}
.tag-cloud{display:flex;flex-wrap:wrap;gap:8px}

/* ===== Stats (5B) ===== */
/* app/Views/partials/chart.php. The svg is preserveAspectRatio="none" so the drawing fills the card's
   width, which means its HEIGHT has to come from here: `height:auto` reads the viewBox's intrinsic
   ratio — 1:1 now — and on a 1168px-wide card that is a 1168px-tall square with the table below it
   pushed off the screen. The labels are HTML over the plot rather than text inside the svg, because
   "none" stretches text along with everything else. --chart-h is set on the figure by the partial. */
.chart{margin:0;--chart-h:180px}
.chart-plot{position:relative;height:var(--chart-h)}
.chart-plot svg{display:block;width:100%;height:100%;color:var(--text);border-bottom:1px solid var(--line)}
/* Over the plot, so it carries the card's ground: the tallest bar is by definition near the top right. */
.chart-max{position:absolute;right:0;top:0;font:500 11px/1 var(--body);color:var(--dim);
  background:var(--bg2);padding:0 0 3px 8px;pointer-events:none}
/* Each label is centred on its own column; `left` is that column's centre as a percentage. */
.chart-x{position:relative;height:16px;margin-top:6px}
.chart-x span{position:absolute;transform:translateX(-50%);font-size:11px;color:var(--dim);white-space:nowrap}
.chart-legend{display:flex;gap:16px;flex-wrap:wrap;margin-top:8px;font-size:13px;color:var(--muted)}
.chart-legend i{display:inline-block;width:10px;height:10px;border-radius:2px;margin-right:6px;vertical-align:-1px}
.chart-legend b{color:var(--text);margin-left:4px}
.stats-bento{grid-template-columns:repeat(2,1fr)}
@media (min-width:760px){.stats-bento{grid-template-columns:repeat(4,1fr)}}
.stats-table{width:100%;border-collapse:collapse;font-size:14px}
.stats-table th,.stats-table td{padding:10px 14px;text-align:right;border-bottom:1px solid var(--line);white-space:nowrap}
.stats-table th:first-child,.stats-table td:first-child{text-align:left}
.stats-table th{font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--muted);font-weight:600}
.stats-table tr:last-child td{border-bottom:0}
.stats-song{display:flex;gap:10px;align-items:center;color:var(--text);text-decoration:none}
.stats-song .th{width:36px;height:36px;border-radius:4px;background:center/cover var(--bg3);flex:0 0 auto}

/* ===== Google sign-in (5C) ===== */
.auth-or{display:flex;align-items:center;gap:12px;color:var(--muted);font-size:12px;margin:16px 0 12px}
.auth-or::before,.auth-or::after{content:"";flex:1;height:1px;background:var(--line)}
.btn-google{display:flex;align-items:center;justify-content:center;gap:10px;width:100%;padding:12px;border-radius:var(--r-ctl);background:var(--bg2);box-shadow:inset 0 0 0 1px var(--line);color:var(--text);font-weight:600;text-decoration:none;transition:box-shadow .2s}
.btn-google:hover{box-shadow:inset 0 0 0 1px var(--line-2)}

/* ===== Security (6B) ===== */
.qr-row{display:flex;gap:20px;align-items:center;flex-wrap:wrap;margin:12px 0 16px}
.qr{width:180px;height:180px;background:#fff;padding:10px;border-radius:var(--r-sm);display:grid;place-items:center}
.qr svg,.qr img{width:100%;height:100%;display:block}
.secret{display:inline-block;font-size:15px;letter-spacing:.08em;padding:8px 12px;border-radius:var(--r-ctl);background:var(--bg3);word-break:break-all}
.code-inp{max-width:160px;font-size:18px;letter-spacing:.2em;text-align:center}
.row-gap{display:flex;gap:10px;align-items:center}
.codes{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:8px;margin-top:12px}
.codes code{padding:10px;border-radius:var(--r-ctl);background:var(--bg3);text-align:center;font-size:15px;letter-spacing:.05em}
.codes-tile{margin-bottom:18px;box-shadow:inset 0 0 0 1.5px var(--acc)}
.sec-grid{display:grid;gap:16px}
@media (min-width:860px){.sec-grid{grid-template-columns:1fr 1fr}}

/* ---- Account: the one page with an irreversible button on it ------------------------------
   The red is not decoration. This block sits beside "download your data", which is harmless, and the
   two must not look alike — the eye should reach the border before the hand reaches the button. */
.tile-danger{box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--bad) 45%,transparent)}
.tile-danger .h3{color:var(--bad)}
.danger-list{margin:2px 0 4px;padding-left:20px;display:flex;flex-direction:column;gap:4px;color:var(--text)}
.danger-list li{font-size:14.5px}
.danger-form{margin-top:8px}
.btn-danger{display:inline-flex;align-items:center;justify-content:center;gap:8px;padding:10px 16px;
  border:0;border-radius:var(--r-ctl);cursor:pointer;font:600 14px/1 inherit;
  background:var(--bad);color:#fff;box-shadow:0 10px 24px -12px var(--bad);
  transition:filter .18s var(--ease),transform .06s}
.btn-danger:hover{filter:brightness(1.06)}
.btn-danger:active{transform:scale(.98)}

/* ===== PWA install banner (6C) ===== */
.install-banner{position:fixed;left:12px;right:12px;bottom:calc(var(--dock-h) + var(--tab-total) + 12px);z-index:70;display:flex;gap:12px;align-items:center;padding:12px 14px;border-radius:var(--r);background:var(--bg2);box-shadow:0 18px 40px rgba(0,0,0,.35),inset 0 0 0 1px var(--line);max-width:520px;margin:0 auto;animation:fadeup .35s var(--ease)}
.install-banner>span{display:grid;place-items:center;width:40px;height:40px;border-radius:var(--r-ctl);background:var(--fill);color:var(--ink);flex:0 0 auto}
.install-banner div{flex:1;min-width:0}
.install-banner b{display:block}
.install-banner small{color:var(--muted)}

/* ===== Mobile feed (7A) ===== */
.feed{display:none}
@media (max-width:899.98px){.has-feed .page{padding-bottom:0;padding-top:24px}.has-feed .ftr{display:none}}
@media (max-width:899.98px){
  .feed{display:block;height:calc(100dvh - var(--hdr-h,64px) - var(--tab-total));margin:-24px -18px 0;overflow-y:auto;scroll-snap-type:y mandatory;scrollbar-width:none;background:var(--bg0,var(--bg))}
  .feed::-webkit-scrollbar{display:none}
  .home-grid.has-feed{display:none}
  body.front.has-feed{padding-bottom:var(--tab-total)}
}
.fcard{position:relative;height:100%;scroll-snap-align:start;scroll-snap-stop:always;overflow:hidden;display:flex;flex-direction:column;justify-content:flex-end;padding:0 18px calc(var(--dock-h) + 22px);isolation:isolate;color:#f3f3f5}
/* The card's atmosphere. The blurred cover is turned up and the veil over it is TINTED rather than
   neutral grey — a grey veil washes the colour straight back out, which is why the feed used to read
   as "dark card with a photo" instead of "this record's colour". The bottom stop stays nearly opaque
   so the title and lyric line keep their contrast whatever the cover is. */
/* Ambient light. --pulse is one number, 0..1, written to <html> by the player every frame: the
   loudness of the moment, with a fast rise and a slow fall. Every blurred cover on the page reads it,
   so while a work is playing the light around it breathes with the music the way a room does when the
   screen is the only lamp in it. Deliberately only opacity and transform — those are the two things a
   browser can animate on the compositor, so this costs nothing on a phone. Nothing is DRAWN around the
   artwork; the picture stays untouched and the light comes from behind it. */
.fbg{position:absolute;inset:-60px;z-index:-2;background:center/cover;filter:blur(44px) saturate(1.7);opacity:.7;transform:scale(1.06)}
/* Only the card that is actually playing breathes. --pulse lives on <html> and is therefore inherited
   by every card in the feed; without this scope all twelve of them pulsed to the one song playing in
   one of them, which is the same mistake the old ring made. */
.fcard.playing .fbg{opacity:calc(.66 + var(--pulse,0) * .3);transform:scale(calc(1.06 + var(--pulse,0) * .06));will-change:opacity,transform}
.fveil{position:absolute;inset:0;z-index:-1;background:
  radial-gradient(120% 70% at 50% 34%,color-mix(in srgb,var(--c1,#0e0f12) 22%,transparent),transparent 70%),
  linear-gradient(180deg,
    color-mix(in srgb,var(--c3,#0e0f12) 22%,rgba(12,11,14,.62)) 0%,
    color-mix(in srgb,var(--c2,#0e0f12) 16%,rgba(12,11,14,.06)) 30%,
    color-mix(in srgb,var(--c3,#0e0f12) 18%,rgba(12,11,14,.6)) 62%,
    color-mix(in srgb,var(--c3,#0e0f12) 10%,rgba(10,9,12,.97)) 100%)}
/* Nothing is drawn around the cover. The artwork is the one thing on this screen the artist made, and
   a permanent animation ringing it competes with the picture instead of serving it. The waveform sits
   below, where a waveform belongs, and doubles as the progress line. */
/* Sized from the CARD'S height, not from its width. A column flex container hands the overflow to
   whichever child is allowed to shrink, and on a tall phone the card overflowed: the cover kept its
   width-derived square and the TITLE — the only other item with overflow:hidden — was squeezed to a
   sliver, so the work's name simply vanished from some cards. Everything below is flex:none now and
   the cover is the one item that gives ground; height in % resolves against the card, which is the
   real visible height (a vh would be the iOS large-viewport lie again). */
.fsq{display:block;flex:0 1 auto;min-height:0;height:var(--cover);width:auto;aspect-ratio:1;margin:0 auto 20px;border-radius:26px;background:center/cover var(--bg3);box-shadow:0 40px 80px -30px rgba(0,0,0,.95),0 0 90px -30px color-mix(in srgb,var(--c1,var(--acc)) 60%,transparent),inset 0 0 0 1px rgba(255,255,255,.12);transform:scale(.94);transition:transform .6s var(--ease),box-shadow .8s var(--ease)}
.fcard.playing .fsq{transform:scale(1)}
/* The right rail (like / comments / share) floats over the card, so the strip stops short of it —
   the same 64px the title and the byline already reserve. */
/* Content width, deliberately: the same inset as the work page's own spectrum, so the two screens
   line up. It was calc(100% - 64px) before only because the action rail hung down beside it. */
.fcard .wave{display:block;flex:none;width:100%;height:66px;color:var(--c1,var(--acc));margin-bottom:8px;filter:brightness(1.15) saturate(1.1)}
.fcard h2{flex:none;font-family:var(--display);font-size:clamp(22px,6.4vw,27px);line-height:1.08;letter-spacing:-.025em;margin:0 0 6px;font-weight:700;text-wrap:balance;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.fcard h2 a{color:inherit;text-decoration:none}
.fby{display:flex;flex:none;align-items:center;gap:8px;color:rgba(243,243,245,.7);font-size:13.5px;flex-wrap:wrap}
.fby a{color:#f3f3f5;font-weight:600;text-decoration:none}
.fby .dot,.dot{width:3px;height:3px;border-radius:50%;background:rgba(243,243,245,.4);flex:none;display:inline-block}
.flyr{flex:none;margin:12px 0;font-family:var(--display);font-size:19px;font-weight:500;line-height:1.3;color:var(--c1,var(--acc));min-height:50px;letter-spacing:-.01em;filter:brightness(1.25) saturate(.9);
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.flyr.pop{animation:fadeup .35s var(--ease)}
.flyr:empty{min-height:0;margin:0}
/* No chevron any more, so the bar runs all the way to the card's own padding — which is also the
   width the spectrum above it uses, so the two line up. */
.fctl{display:flex;flex:none;align-items:center;gap:14px}
.fpb{width:54px;height:54px;border-radius:50%;background:var(--c1,var(--acc));color:#111216;display:grid;place-items:center;flex:none;box-shadow:0 12px 30px -8px var(--c1,var(--acc)),inset 0 0 0 1px rgba(255,255,255,.25);transition:transform .2s var(--ease)}
.fpb:active{transform:scale(.95)}
.fbar{flex:1;min-width:0}
.fbar .tr{height:14px;display:flex;align-items:center;cursor:pointer}
.fbar .tr::before{content:"";position:absolute;left:0;right:0;height:4px;border-radius:2px;background:rgba(255,255,255,.16)}
.fbar .tr{position:relative}
.fbar .tr i{position:relative;display:block;height:4px;width:0;border-radius:2px;background:linear-gradient(90deg,var(--c2,var(--acc2)),var(--c1,var(--acc)));transition:width .25s linear}
.fbar .t{display:flex;justify-content:space-between;font-size:11.5px;color:rgba(243,243,245,.65);font-variant-numeric:tabular-nums;margin-top:4px}
.fctl .icb{background:rgba(255,255,255,.08);color:#f3f3f5;box-shadow:inset 0 0 0 1px rgba(255,255,255,.18)}
/* The rail sits BESIDE the cover, not below it. It used to hang low enough that the title, the
   byline and the lyric line all had to reserve 64px on the right for it — which is why a title as
   short as "Şu Dağlar Kömürdendir" wrapped onto a second line. Moved up, the text gets the full
   width back and the actions land under the thumb rather than in the middle of the card. */
/* 17vh became 17% + a floor. On iOS, vh measures the LARGE viewport — the one you would have if the
   URL bar were hidden — while the card's control row is anchored to the bottom you can actually see.
   On an iPhone 17 Pro the difference is the whole browser chrome, so the rail was pushed down until
   the Paylaş circle sat on top of the chevron. % resolves against the card, which is the real
   height; the min() is a hard floor that keeps the rail clear of the control row on any screen, and
   the max() stops it from climbing under the header on a short one. */
.frail{position:absolute;right:12px;
  top:max(calc(var(--hdr-h,64px) + 8px), min(calc(var(--hdr-h,64px) + 17%), calc(100% - var(--dock-h) - 300px)));
  z-index:2;display:flex;flex-direction:column;gap:14px;align-items:center}
.frail button,.frail a{display:flex;flex-direction:column;align-items:center;gap:3px;font-size:11px;font-weight:600;color:#f3f3f5;text-decoration:none;font-variant-numeric:tabular-nums;background:none;border:0;padding:0}
.frail .ic{width:44px;height:44px;border-radius:50%;display:grid;place-items:center;background:rgba(20,21,25,.45);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);box-shadow:inset 0 0 0 1px rgba(255,255,255,.18)}
.frail .like.on .ic{color:#ff6b7f}
.fmore{justify-content:center;align-items:center;text-align:center;background:var(--bg)}
.fmore-in{display:flex;flex-direction:column;gap:12px;align-items:center;padding:0 24px}
.fmore h2{font-family:var(--display);font-size:24px;margin:0;color:var(--text)}
.fmore .muted{color:var(--muted)}

/* tabbar (7A): opaque enough to read over scrolling content */
.tabbar{background:color-mix(in srgb,var(--bg) 94%,transparent);backdrop-filter:blur(20px) saturate(1.3);-webkit-backdrop-filter:blur(20px) saturate(1.3)}
.tabbar a span{font-weight:600}

/* mobile polish (7A): card meta on one line, section heads, song actions */
/* The duration and the category are anonymous flex items with nothing to shrink, so the only
   flexible item — the artist's link — absorbed the whole deficit: measured 0.0px wide on four of
   eight realistic Turkish names, leaving cards that read "· 3:48 · Türk Halk M". Letting the line
   wrap costs a few pixels of height and keeps the name. */
.card-song .meta span{white-space:normal;overflow:hidden;overflow-wrap:anywhere}
.card-song .meta span a{overflow:hidden;text-overflow:ellipsis;min-width:0}
.section-head{flex-wrap:wrap;gap:4px 12px}
@media (max-width:639.98px){
  /* This was a four-column grid. There are up to SEVEN buttons here, so items five onward landed in
     row two sized by row one's tracks — "Kendi cover'ını ekle" in a track cut for "Paylaş" — and the
     overflow was clipped by the hero's overflow:hidden. Nobody could see the button, let alone press
     it. A wrapping flex row sizes every button to its own label and starts a new line when it has to. */
  .song-hero-in{gap:14px}
  .song-cover{width:var(--cover);max-width:88%;margin:0 auto}
}
[data-share].done{color:var(--ok)}

/* ===== Song page — Stüdyo (7C) ===== */
.studio-song .song-hero{padding-top:28px}
.studio-song .song-hero-in{align-items:center}
@media (max-width:759.98px){.studio-song .song-hero-in{text-align:center}.studio-song .song-info{align-items:center}.studio-song .song-by,.studio-song .song-tags,.studio-song .song-cover-of{justify-content:center}.studio-song .song-cover{width:var(--cover);max-width:88%;margin:0 auto}}
.studio-song .eyebrow{display:flex;align-items:center;gap:6px;flex-wrap:wrap;color:var(--c1,var(--acc));filter:saturate(.9) brightness(1.1)}
[data-theme="light"] .studio-song .eyebrow{filter:saturate(1.1) brightness(.8)}
@media (prefers-color-scheme:light){:root:not([data-theme]) .studio-song .eyebrow{filter:saturate(1.1) brightness(.8)}}
.studio-song .eyebrow a{color:inherit}
.studio-song .song-info h1{font-size:clamp(32px,5vw,52px);font-weight:700;letter-spacing:-.03em;line-height:1.02;text-wrap:balance}
.studio-song .song-by{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.studio-song .song-by .dot{background:var(--dim)}
.song-player{display:flex;align-items:center;gap:14px;width:100%;max-width:560px;margin-top:6px}
.song-player .fpb{background:var(--c1,var(--acc));color:var(--ink);box-shadow:0 12px 30px -8px var(--c1,var(--acc)),inset 0 0 0 1px rgba(255,255,255,.25)}
.song-player .fbar .tr::before{background:color-mix(in srgb,var(--text) 14%,transparent)}
.song-player .fbar .t{color:var(--muted)}
.studio-song .wave{display:block;width:100%;max-width:560px;height:48px;color:var(--c1,var(--acc));margin:2px 0 6px}
.stabs{display:flex;gap:2px;padding:4px;border-radius:var(--r-sm);background:var(--bg2);box-shadow:inset 0 0 0 1px var(--line);margin:0 0 18px}
.stabs button{flex:1;padding:10px;border-radius:calc(var(--r-sm) - 3px);color:var(--muted);font-weight:600;font-size:13.5px;background:none;border:0;cursor:pointer;transition:background .2s,color .2s;font:inherit;font-weight:600}
.stabs button[aria-selected="true"]{background:var(--bg4);color:var(--text)}
.stabs button:disabled{opacity:.4;cursor:not-allowed}
.stabs button .muted{margin-left:4px}
.stab-hint{display:flex;align-items:center;gap:6px;margin:0 0 10px}
.studio-song .lyrics{font-size:clamp(21px,2.4vw,26px);line-height:1.35;font-weight:600;letter-spacing:-.015em;color:var(--text)}
.studio-song .lyrics p{margin:0 0 12px}
.studio-song .lyrics[data-timed] p{color:var(--dim);transition:color .35s,transform .35s var(--ease)}
.studio-song .lyrics[data-timed] p.past{color:var(--muted)}
.studio-song .lyrics[data-timed] p.now{color:var(--text);transform:translateX(8px)}
.studio-song .lyrics[data-timed] p.now::before{display:none}
.studio-song .story{color:var(--muted);font-size:16px;line-height:1.7;max-width:62ch}
.studio-song .comments-block{margin-top:34px}
.studio-song .song-side .artist-card{margin-top:0}

.card-song .pl{position:absolute;right:10px;top:10px;display:grid;place-items:center;width:30px;height:30px;border-radius:var(--r-ctl);background:rgba(15,16,19,.55);color:#fff;backdrop-filter:blur(6px);opacity:0;transition:opacity .2s}
.card-song:hover .pl,.card-song .pl:focus-visible{opacity:1}
@media (hover:none){.card-song .pl{opacity:1}}
.pl-picker .pl-song{margin:-6px 0 12px}
.pl-list{list-style:none;margin:0 0 14px;padding:0;display:flex;flex-direction:column;gap:6px;max-height:40vh;overflow:auto}
.pl-toggle{display:flex;align-items:center;gap:10px;width:100%;padding:8px 10px;border-radius:var(--r-ctl);background:var(--bg3);text-align:left;color:var(--text)}
.pl-toggle .pl-check{display:grid;place-items:center;width:22px;height:22px;border-radius:6px;box-shadow:inset 0 0 0 1px var(--line-2);color:transparent;flex:none;transition:background .15s,color .15s}
.pl-toggle.on .pl-check{background:var(--fill);color:var(--ink);box-shadow:none}
.pl-toggle .pl-name{display:flex;flex-direction:column;min-width:0}
.pl-toggle .pl-name b{display:block;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pl-new{display:flex;flex-direction:column;gap:8px;margin-top:6px}
.pl-new .row-gap .inp{flex:1}
.pl-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:12px}
.pl-tile{display:flex;flex-direction:column;gap:6px;padding:10px;border-radius:var(--r-sm);background:var(--bg2);box-shadow:inset 0 0 0 1px var(--line);color:var(--text)}
/* Two lines, like the song cards: a list's title is the only way to tell it apart in the library,
   and at 136px a single ellipsed line showed about fourteen characters. */
.pl-tile b{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;font-family:var(--display);font-weight:600;line-height:1.3;white-space:normal;overflow:hidden;overflow-wrap:anywhere}
.pl-tile-new .inp{width:100%}
/* The mosaic fills the space it has. Every call site used to pad the cover list to four with nulls,
   so a three-song list drew a fourth empty square — a dark hole in the corner that read as a failed
   image. The count picks the shape instead: one fills it, two split it, three give the first cover
   the whole left column. */
.pl-collage{display:grid;grid-template-columns:1fr 1fr;grid-auto-rows:1fr;gap:2px;aspect-ratio:1;border-radius:var(--r-sm);overflow:hidden;background:var(--bg3)}
.pl-collage span{background:var(--bg4) center/cover;min-width:0;min-height:0}
.pl-collage.n1{grid-template-columns:1fr}
.pl-collage.n3 span:first-child{grid-row:span 2}
/* One column, or the icon is centred inside the LEFT HALF of the tile — it inherits the two-column
   template above and auto-places into cell 1. Visible on the library's "new list" tile every load. */
.pl-collage.empty{grid-template-columns:1fr;display:grid;place-items:center;color:var(--dim)}
.pl-collage.empty .i{width:32px;height:32px}
.pl-hero{display:grid;grid-template-columns:min(220px,40vw) 1fr;gap:20px;align-items:end;margin-bottom:22px}
.pl-hero .pl-collage{width:100%}
.pl-desc{color:var(--muted);max-width:60ch}
.pl-edit{margin-bottom:20px}
.pl-songs{list-style:none;margin:0;padding:0;counter-reset:none}
.pl-songs .pl-no{width:22px;text-align:right;color:var(--dim);font-size:12px;flex:none}
.pl-songs .p{display:grid;place-items:center;width:36px;height:36px;border-radius:50%;background:var(--fill);color:var(--ink);flex:none}
.pl-tools{display:flex;gap:2px;align-items:center}
.pl-tools form{display:contents}
.icon-btn:disabled{opacity:.3;cursor:default}
.i.rot90{transform:rotate(90deg)}
@media (max-width:639.98px){.pl-hero{grid-template-columns:1fr}.pl-hero .pl-collage{width:min(220px,60vw)}}
/* The owner's controls stay on phones. display:none left whoever made the list with no up, no down
   and no remove, and nothing on the page saying why. Three 44px targets do not fit beside the title
   at 360px, so the group takes its own line under the row rather than being shrunk away. */
@media (max-width:639.98px){
  .pl-songs .song-row{flex-wrap:wrap}
  .pl-tools{order:9;flex-basis:100%;justify-content:flex-end;gap:6px;margin-top:2px}
  .pl-tools .pl-tool{width:44px;height:44px}
}
html.pjax-loading{cursor:progress}
.pjax-bar{position:fixed;left:0;top:0;height:2px;width:0;background:var(--acc);z-index:200;transition:width .3s ease,opacity .3s;opacity:0}
.pjax-bar.on{opacity:1}

/* ---------- Phase 9b: avatars, bell, notifications, embed modal ---------- */
.avatar.has-img{background:var(--bg3);overflow:hidden}
.avatar.has-img img{width:100%;height:100%;object-fit:cover;display:block}
.avatar-row{display:flex;gap:16px;align-items:center;margin-bottom:16px}
.avatar-pick{cursor:pointer;align-self:flex-start}
.bell{position:relative}
.bell-n{position:absolute;top:2px;right:2px;min-width:16px;height:16px;padding:0 4px;border-radius:999px;background:var(--fill);color:var(--ink);font:700 10px/16px var(--body);text-align:center}
.notif-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}
.notif{display:flex;gap:12px;align-items:center;padding:10px 12px;border-radius:var(--r-sm);background:var(--bg2);box-shadow:inset 0 0 0 1px var(--line)}
.notif.unread{box-shadow:inset 0 0 0 1px var(--acc),inset 3px 0 0 var(--acc)}
.notif-body{display:flex;flex-direction:column;gap:2px;min-width:0;color:var(--text)}
.emb-preview{margin-top:14px}
.emb-preview iframe{width:100%}
[data-embed-code]{width:100%;font:12px/1.4 ui-monospace,monospace}

/* ==========================================================================
   Player ("Sahne", Phase 10): full-width bar + the stage that opens above it with the lyrics.
   Phones: the bar sits above the tab bar; the stage is a full-screen sheet.
   ========================================================================== */
.dock{position:fixed;left:0;right:0;bottom:var(--tab-total);z-index:46;display:flex;flex-direction:column;background:var(--bg2);border-top:1px solid var(--line-2);box-shadow:0 -30px 60px -30px rgba(0,0,0,.9),inset 0 1px 0 color-mix(in srgb,var(--c1,var(--acc)) 35%,transparent);isolation:isolate;overflow:hidden;transition:background .8s}
@media (min-width:900px){.dock{bottom:0}}
.dock-bg{position:absolute;inset:-60px;z-index:-1;background:center/cover;filter:blur(60px) saturate(1.4);
  opacity:calc(.2 + var(--pulse,0) * .2);pointer-events:none;transform:scale(calc(1 + var(--pulse,0) * .05));will-change:opacity,transform}
.dock.open .dock-bg{opacity:calc(.3 + var(--pulse,0) * .28)}
/* progress line on the top edge */
.dock-rail{position:absolute;left:0;right:0;top:0;height:14px;cursor:pointer;z-index:2;display:flex;align-items:flex-start}
.dock-rail::before{content:"";position:absolute;left:0;right:0;top:0;height:3px;background:color-mix(in srgb,var(--text) 14%,transparent)}
.dock-rail i{position:relative;display:block;height:3px;width:0;background:var(--c1,var(--acc));transition:width .25s linear,background .8s}
.dock-rail:hover::before,.dock-rail:focus-visible::before{height:5px}
.dock-rail:hover i,.dock-rail:focus-visible i{height:5px}
/* focus ring for .dock-rail is defined at the end of this file */
/* the always-visible bar */
.dock-bar{height:var(--dock-h);display:grid;grid-template-columns:1fr auto;align-items:center;gap:10px;padding:8px 12px 6px;max-width:1600px;margin:0 auto;width:100%}
@media (min-width:900px){.dock-bar{grid-template-columns:minmax(0,3fr) minmax(0,4fr) minmax(0,4fr) auto;gap:16px;padding:6px 24px 4px}}
.dock-now{display:flex;align-items:center;gap:12px;min-width:0}
.dock-art{width:52px;height:52px;border-radius:var(--r-ctl);background:var(--bg4) center/cover;box-shadow:inset 0 0 0 1px var(--line-2);flex:none;padding:0}
.dock-meta{min-width:0;display:flex;flex-direction:column;flex:1}
.dock-title{font-family:var(--display);font-weight:600;font-size:14.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.dock-by{color:var(--muted);font-size:12.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.dock-lyr-m{display:none;text-align:left;color:var(--c1,var(--acc-text));font-size:12.5px;font-style:italic;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:var(--display);font-weight:500;padding:0;margin-top:1px}
.dock-like{flex:none}
.dock-ctl{display:flex;align-items:center;justify-content:center;gap:4px}
.dock-btn{width:40px;height:40px;border-radius:50%;display:grid;place-items:center;color:var(--muted);transition:color .2s,background .2s}
.dock-btn:hover{color:var(--text);background:color-mix(in srgb,var(--text) 8%,transparent)}
.dock-play{width:46px;height:46px;border-radius:50%;background:var(--c1,var(--acc));color:var(--c-ink,var(--ink));display:grid;place-items:center;box-shadow:0 10px 24px -8px var(--c1,var(--acc)),inset 0 0 0 1px rgba(255,255,255,.25);transition:background .8s,color .8s,transform .2s var(--ease);margin:0 4px}
.dock-play:active{transform:scale(.94)}
.dock-seek .i,.dock-seek svg{width:22px;height:22px}
/* Nothing to skip to: hidden rather than greyed out, so the bar does not offer a control that cannot
   do anything. aria-hidden is set alongside display:none by the script. */
body.one-track #dock-prev,body.one-track #dock-next,body.one-track #dock-shuffle{display:none!important}
.dock-time{margin-left:10px;font-size:12px;color:var(--muted);white-space:nowrap}
.dock-time .sep{margin:0 4px;color:var(--dim)}
/* live lyric line → opens the stage */
.dock-lyr{display:flex;align-items:center;justify-content:flex-end;gap:12px;min-width:0;text-align:right;padding:4px 0}
.dock-lyr-in{display:flex;flex-direction:column;min-width:0;align-items:flex-end}
.dock-lyr .eyebrow{font-size:10px}
.dock .eyebrow-acc{color:var(--c1,var(--acc-text))}
.dock-lyr-line{font-family:var(--display);font-weight:600;font-size:16px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;color:var(--text);transition:opacity .3s}
.dock-lyr-line.pop{animation:fadeup .35s var(--ease)}
.dock-lyr-line.idle{color:var(--muted);font-weight:500;font-size:14px}
.dock-chev{transition:transform .4s var(--ease)}
.dock.open .dock-chev{transform:rotate(180deg)}
.dock-vol{display:none;align-items:center;gap:4px}
.dock-vol input[type=range]{width:84px;accent-color:var(--c1,var(--acc));cursor:pointer}
.dock-mode{position:relative}
.dock-mode[aria-pressed="true"],.dock-mode[data-mode="all"],.dock-mode[data-mode="one"]{color:var(--c1,var(--acc-text))}
.dock-mode .one{display:none;position:absolute;right:4px;bottom:4px;font:700 9px/1 var(--body);font-style:normal;background:var(--c1,var(--acc));color:var(--c-ink,var(--ink));border-radius:999px;padding:1px 3px}
.dock-mode[data-mode="one"] .one{display:block}
@media (min-width:900px){.dock-vol{display:flex}}
@media (max-width:899.98px){
  .dock-bar{height:64px;padding:8px 10px 4px}
  .dock-art{width:44px;height:44px}
  .dock-ctl .dock-mode,.dock-time,#dock-prev,.dock-lyr,.dock-like{display:none}
  .dock-play{width:44px;height:44px}
  /* On the phone bar the two buttons beside play are ±10 s; "next" only appears when the queue
     actually has a next. */
  .dock-seek{display:grid}
  .dock-by{display:none}.dock-lyr-m{display:block}
}
/* ----- the stage ----- */
.stage{position:relative;height:0;overflow:hidden;transition:height .45s var(--ease)}
.dock.open .stage{height:min(520px,calc(100dvh - 140px))}
/* Two rows now: the cover+title block, then the waveform under it. Explicit rows because the lyric
   column spans both — with implicit auto rows its height was shared between them and the waveform
   drifted to the bottom of the panel instead of sitting under the title. */
.stage-in{height:100%;max-width:1600px;margin:0 auto;padding:36px 24px 0;display:grid;grid-template-columns:minmax(0,5fr) minmax(0,7fr);grid-template-rows:auto 1fr;gap:12px 40px}
.stage-info{display:flex;gap:24px;align-items:flex-start;min-width:0}
.stage-art{width:200px;height:200px;border-radius:var(--r);background:var(--bg4) center/cover;box-shadow:0 30px 60px -25px rgba(0,0,0,.9),inset 0 0 0 1px rgba(255,255,255,.1);flex:none;display:block}
.stage-meta{min-width:0;display:flex;flex-direction:column;gap:6px}
/* The waveform in the player. Same canvas, same code as the feed card's — only wider, and it follows
   whatever is playing rather than one work. It is the second row of .stage-in, so on desktop it sits
   under the cover and title in the left column and on a phone it spans the whole width. */
.stage-wave{display:block;width:100%;max-width:440px;height:60px;color:var(--c1,var(--acc));margin:14px 0 0;align-self:start;filter:brightness(1.15) saturate(1.1)}
.stage-title{font-size:clamp(22px,2.4vw,30px);font-weight:700;letter-spacing:-.02em;line-height:1.05;margin:4px 0 0}
.stage-title a{color:var(--text)}
.stage-by{margin:0;color:var(--muted);font-size:15px;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.stage-by a{color:var(--text);font-weight:600}
.stage-by .dot{background:var(--dim)}
.stage-actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:12px}
.stage-story{color:var(--muted);font-size:13.5px;line-height:1.6;margin:14px 0 0;max-width:46ch;display:-webkit-box;-webkit-line-clamp:5;-webkit-box-orient:vertical;overflow:hidden}
.stage-lyrics:focus-visible{outline:2px solid var(--acc);outline-offset:-2px}
.stage-lyrics{grid-column:2;grid-row:1 / span 2;position:relative;overflow-y:auto;scrollbar-width:none;padding:120px 8px 260px 18px;-webkit-mask-image:linear-gradient(transparent,#000 14%,#000 82%,transparent);mask-image:linear-gradient(transparent,#000 14%,#000 82%,transparent);scroll-behavior:smooth;outline:0}
.stage-lyrics::-webkit-scrollbar{display:none}
.stage-lyrics p{margin:0;padding:6px 0;font-family:var(--display);font-weight:600;font-size:clamp(20px,2vw,26px);line-height:1.3;color:color-mix(in srgb,var(--text) 42%,transparent);transition:color .35s,transform .35s var(--ease);letter-spacing:-.01em;text-wrap:pretty;transform-origin:left center}
.stage-lyrics p.gap{padding:10px 0}
.stage-lyrics p[data-t]:not([data-t=""]){cursor:pointer}
.stage-lyrics p[data-t]:not([data-t=""]):hover{color:var(--muted)}
.stage-lyrics p.past{color:color-mix(in srgb,var(--text) 62%,transparent)}
.stage-lyrics p.now{color:var(--text);transform:scale(1.04)}
.stage-lyrics p.now::before{content:"";position:absolute;left:-16px;width:4px;height:1.1em;border-radius:2px;background:var(--c1,var(--acc));transform:translateY(.18em)}
.stage-lyrics p{position:relative}
.stage-lyrics:not(.timed) p{color:var(--text);font-weight:500}
.stage-lyrics .stage-empty{color:var(--muted);font-weight:500;font-size:16px;font-family:var(--body)}
.stage-lyrics .stage-empty a{color:var(--acc-text);font-weight:600}
.stage-close{position:absolute;right:16px;top:16px;z-index:3;display:none}
/* Height as well as width. Gated on width alone, a phone in LANDSCAPE — 932 CSS px on an iPhone Pro
   Max, 915 on a Pixel Pro — fell on the desktop side and got the 520px side panel instead of the
   fullscreen one: the site header and a strip of the song page stayed visible above the "fullscreen"
   lyrics, Paylaş and "Eser sayfası" rendered below the panel's own overflow:hidden and vanished, the
   waveform sat 260px past its bottom edge, and the highlighted lyric scrolled to a point outside the
   visible area — so while a song played, the current line was never on screen. */
@media (max-width:899.98px),(max-height:620px){
  .dock.open{top:0;bottom:0;border-top:0}
  .dock.open .stage{height:auto;flex:1;min-height:0;display:flex;flex-direction:column}
  .dock.open .dock-rail{top:auto;bottom:calc(64px + env(safe-area-inset-bottom) + 66px)}
  .dock.open .dock-bar{height:auto;grid-template-columns:1fr;gap:6px;padding:10px 14px calc(14px + env(safe-area-inset-bottom))}
  .dock.open .dock-now{order:2;display:none}
  .dock.open .dock-ctl{order:1;justify-content:space-between;gap:0;padding:0 6px;flex-wrap:wrap}
  .dock.open .dock-ctl .dock-mode,.dock.open #dock-prev{display:grid}
  /* One work in the queue: prev/next/shuffle are gone, so centre what is left — the ±10 s pair around
     play — and float repeat (still meaningful on a single track) out to the edge. */
  body.one-track .dock.open .dock-ctl{justify-content:center;gap:14px;position:relative}
  body.one-track .dock.open #dock-repeat{position:absolute;right:8px;top:50%;transform:translateY(-50%)}
  .dock.open .dock-time{display:block;margin:0 0 4px;text-align:center;order:-1;width:100%}
  .dock.open .dock-play{width:64px;height:64px}
  .stage-in{display:flex;flex-direction:column;padding:56px 22px 0;gap:14px;min-height:0}
  .stage-info{gap:14px;align-items:center;flex:none}
  .stage-art{width:72px;height:72px;border-radius:var(--r-sm)}
  .stage-title{font-size:20px}
  .stage-by{font-size:13px}
  .stage-actions{display:none}
  .stage-story{display:none}
  .stage-lyrics{flex:1;padding:90px 0 50vh 16px}
  .stage-lyrics p{font-size:24px;padding:8px 0}
  .stage-close{display:grid;left:12px;right:auto;top:10px}
  .stage-close .i{transform:none}
}
@media (min-width:900px){.dock.open .stage-close{display:none}}
body.stage-open{overflow:hidden}
@media (min-width:900px){body.stage-open{overflow:auto}}

/* ==========================================================================
   Home ("Sahne"): weekly pick hero + this week's most played + new works. Desktop only —
   phones get the feed. Everything inside is real data; no stat tiles.
   ========================================================================== */
.sahne{display:none}
@media (min-width:900px){
  .sahne{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:20px;margin:6px 0 30px}
  .sahne .hero{grid-column:span 8}.sahne .top{grid-column:span 4}
}
.sahne .hero{position:relative;min-height:340px;border-radius:var(--r-lg);overflow:hidden;background:var(--bg2);box-shadow:inset 0 0 0 1px var(--line),var(--card-shadow);isolation:isolate;display:flex;flex-direction:column;justify-content:flex-end;padding:28px}
.sahne .hero-bg{position:absolute;inset:-40px;z-index:-2;background:center/cover;filter:blur(60px) saturate(1.2);opacity:.5}
.sahne .hero::after{content:"";position:absolute;inset:0;z-index:-1;background:radial-gradient(70% 90% at 85% 15%,color-mix(in srgb,var(--c1,var(--acc)) 35%,transparent),transparent 65%),linear-gradient(180deg,color-mix(in srgb,var(--bg2) 20%,transparent),color-mix(in srgb,var(--bg2) 92%,transparent) 80%)}
.sahne .hero .eyebrow{position:absolute;left:28px;top:24px;color:var(--acc-text)}
.sahne .hero .eyebrow::before{content:"";width:6px;height:6px;border-radius:50%;background:var(--acc)}
.sahne .hero-line{display:flex;align-items:flex-end;gap:24px;margin:0;padding:0}
.sahne .hero-art{width:168px;height:168px;border-radius:var(--r);background:var(--bg3);box-shadow:0 24px 50px -20px rgba(0,0,0,.8),inset 0 0 0 1px rgba(255,255,255,.1);flex:none;display:block;overflow:hidden}
.sahne .hero-art img{width:100%;height:100%;object-fit:cover;display:block}
.sahne .hero-meta{flex:1;min-width:0;display:flex;flex-direction:column;gap:8px}
.sahne .hero-meta h2{font-size:clamp(28px,3.2vw,40px);font-weight:700;letter-spacing:-.025em;line-height:1.02}
.sahne .hero-meta h2 a{color:var(--text)}
.sahne .hero-meta .fby{color:var(--muted);font-size:15px}
.sahne .hero-meta .fby a{color:var(--text)}
.sahne .hero-meta .fby .dot{background:var(--dim)}
.sahne .hero-quote{font-family:var(--display);font-weight:500;font-style:italic;font-size:19px;line-height:1.35;color:var(--text);opacity:.85;margin:4px 0 0;max-width:46ch}
.sahne .hero-cta{display:flex;gap:8px;align-items:center;flex:none}
.sahne .hero-cta .btn-play{height:52px;padding:0 22px 0 18px;border-radius:26px;background:var(--text);color:var(--bg);display:inline-flex;align-items:center;gap:10px;font-weight:600;font-size:15px;transition:transform .2s var(--ease)}
.sahne .hero-cta .btn-play:hover{transform:translateY(-1px)}
.sahne .top{border-radius:var(--r-lg);background:var(--bg2);box-shadow:inset 0 0 0 1px var(--line),var(--card-shadow);padding:22px 20px;display:flex;flex-direction:column;gap:4px;min-height:340px}
/* The panel matches the hero's height so the two columns line up — but only while it has enough rows
   to fill it. With one or two works that floor was 200px of nothing, which is what made a working site
   look broken. Fewer than three: shrink to the content and sit at the top. */
.sahne .top[data-count="1"],.sahne .top[data-count="2"]{min-height:0;align-self:start}
.sahne .top .section-head{margin-bottom:8px}
.sahne .top .row{display:flex;align-items:center;gap:12px;padding:8px;border-radius:var(--r-ctl);transition:background .2s;cursor:pointer}
.sahne .top .row:hover,.sahne .top .row.playing{background:color-mix(in srgb,var(--text) 5%,transparent)}
.sahne .top .row.playing .n{color:var(--acc-text)}
.sahne .top .no{width:18px;text-align:center;font-size:13px;color:var(--dim);font-weight:600;font-variant-numeric:tabular-nums}
.sahne .top .th{width:44px;height:44px;border-radius:var(--r-ctl);flex:none;background:center/cover var(--bg3);box-shadow:inset 0 0 0 1px var(--line-2);display:grid;place-items:center;color:#fff}
.sahne .top .th .i{opacity:0;transition:opacity .2s;filter:drop-shadow(0 2px 6px rgba(0,0,0,.6))}
.sahne .top .row:hover .th .i{opacity:1}
.sahne .top .tx{min-width:0;flex:1}
.sahne .top .n{font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block;color:var(--text);font-size:14px}
.sahne .top .m{font-size:12px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block}
.sahne .top .d{font-size:12px;color:var(--dim);font-variant-numeric:tabular-nums}
.sahne .top .more{margin-top:auto;align-self:flex-end;color:var(--acc-text);font-weight:600;font-size:13px;display:inline-flex;align-items:center;gap:4px;padding-top:8px}
.sahne .row-3{grid-column:span 12;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:20px}
.sahne .mini{border-radius:var(--r);background:var(--bg2);box-shadow:inset 0 0 0 1px var(--line),var(--card-shadow);padding:18px 20px;display:flex;flex-direction:column;gap:10px;min-width:0}
.sahne .mini .tl{font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--dim);font-weight:700;display:flex;align-items:center;gap:6px}
.sahne .mini .tags{display:flex;flex-wrap:wrap;gap:8px}
.sahne .artists{display:flex;flex-direction:column;gap:4px}
.sahne .artists a{display:flex;align-items:center;gap:10px;padding:6px;border-radius:var(--r-ctl);color:var(--text);font-weight:600;font-size:14px}
.sahne .artists a:hover{background:color-mix(in srgb,var(--text) 5%,transparent)}
.sahne .artists .av{width:36px;height:36px;border-radius:50%;background:center/cover linear-gradient(135deg,var(--acc),var(--acc2));display:grid;place-items:center;font:700 15px var(--display);color:var(--ink);flex:none}
.sahne .artists small{color:var(--muted);font-weight:400;margin-left:auto;white-space:nowrap}
.sahne .pulse{display:flex;flex-direction:column;gap:2px;margin-top:auto}
.sahne .pulse b{font-family:var(--display);font-size:32px;font-weight:700;letter-spacing:-.03em;font-variant-numeric:tabular-nums;line-height:1}
.sahne .pulse span{color:var(--muted);font-size:12.5px}
.sahne .pulse em{font-style:normal;color:var(--acc-text);font-weight:600}
.tg{padding:7px 12px;border-radius:var(--r-ctl);background:color-mix(in srgb,var(--text) 5%,transparent);box-shadow:inset 0 0 0 1px var(--line);font-size:12.5px;font-weight:600;color:var(--text);text-decoration:none;transition:background .2s}
.tg:hover{background:color-mix(in srgb,var(--acc) 18%,transparent)}
.fpb.sm{width:42px;height:42px}.fpb.xs{width:34px;height:34px;box-shadow:inset 0 0 0 1px rgba(255,255,255,.2)}
/* home (desktop): the hero replaces the big headline; keep it for phones/grid view */
@media (min-width:900px){.home-grid.has-sahne .hero-row{display:none}}


/* ==========================================================================
   Feed (Phase 11): the strips between batches, the sentinel and its spinner.
   ========================================================================== */
.stream-batch{display:contents}
.strip{grid-column:1/-1}
.fsentinel{height:1px;scroll-snap-align:none;flex:none}
body.modal-open .hdr,body.modal-open #main,body.modal-open .ftr,body.modal-open .tabbar,body.modal-open .dock{pointer-events:none}
/* The stage is a full-screen surface on phones and it covers the tab bar completely. The tab bar was
   still in the layout underneath it: invisible, still focusable, and every tap on it landed on the
   player's controls instead — a row of navigation that silently did nothing. It closes with the
   chevron at the top left. */
body.stage-open .tabbar{display:none}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
.stream-end{display:flex;flex-direction:column;align-items:center;gap:10px;padding:26px 0 6px;min-height:60px}
.stream-end.is-end [data-stream-more]{display:none}
.stream-end.is-end::after{content:"Şimdilik hepsi bu kadar.";color:var(--dim);font-size:13.5px}
.stream-end.is-wall::after{content:"Devamı için listeye geç.";color:var(--dim);font-size:13.5px}
.stream-spin{display:flex;align-items:center;gap:8px;color:var(--muted);font-size:13.5px}
.spin{width:16px;height:16px;border-radius:50%;border:2px solid color-mix(in srgb,var(--text) 18%,transparent);border-top-color:var(--acc);animation:spin .7s linear infinite;display:inline-block}
@keyframes spin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion:reduce){.spin{animation-duration:2s}}

.strip{margin:12px 0 2px;padding:18px 20px 20px;border-radius:var(--r);background:var(--bg2);box-shadow:inset 0 0 0 1px var(--line),var(--card-shadow)}
.strip-head{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;margin-bottom:14px}
.strip-head h2{display:flex;align-items:center;gap:8px}
.strip-head h2 .i{color:var(--acc-text)}
.strip-head .link{margin-left:auto;display:inline-flex;align-items:center;gap:2px}
.strip-row{display:grid;grid-auto-flow:column;grid-auto-columns:minmax(132px,1fr);gap:14px;overflow-x:auto;scrollbar-width:none;padding-bottom:2px}
.strip-row::-webkit-scrollbar{display:none}
@media (min-width:900px){.strip-row{grid-auto-columns:minmax(150px,1fr)}}
.strip-card{display:flex;flex-direction:column;gap:6px;min-width:0}
.strip-art{position:relative;aspect-ratio:1;border-radius:var(--r-sm);background:var(--bg3) center/cover;padding:0;overflow:hidden;box-shadow:inset 0 0 0 1px var(--line)}
.strip-play{position:absolute;right:8px;bottom:8px;width:34px;height:34px;border-radius:50%;background:var(--fill);color:var(--ink);display:grid;place-items:center;opacity:0;transform:translateY(6px);transition:opacity .2s,transform .25s var(--ease)}
.strip-art:hover .strip-play,.strip-art:focus-visible .strip-play{opacity:1;transform:none}
@media (hover:none){.strip-play{opacity:1;transform:none}}
.strip-name{font-family:var(--display);font-weight:600;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.strip-by{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}


/* ---- Light theme: status colours (accessibility fix) -----------------------
   The two light-theme blocks above define bg/text/muted/acc/line but NOT --ok / --warn / --bad, so
   the dark-theme values stayed in place on a cream ground. Measured there: form error text 3.0:1,
   the "askıda" badge 1.94:1 — a validation message you cannot read, in the one place it matters.
   Values below clear 4.5:1 on --bg2. Defined for BOTH the explicit toggle and the system default. */
:root[data-theme="light"]{--ok:#1e7a4d;--warn:#8a5a06;--bad:#b3261e}
@media (prefers-color-scheme:light){:root:not([data-theme="dark"]){--ok:#1e7a4d;--warn:#8a5a06;--bad:#b3261e}}

/* ---- Touch: make the page feel like an app, not a document ----------------- */

/* Android draws a grey box on every tap without this. Nothing says "web page" faster. */
html{-webkit-tap-highlight-color:transparent}

/* Long-press on the cover offered "Save image" while the card is a swipe target. */
button,.chip,.tabbar a,.frail button,.frail a,.fpb,.dock-btn,.icb,.fsq,.strip-art{
  -webkit-touch-callout:none;-webkit-user-select:none;user-select:none}

/* Pull-to-refresh reloaded the whole app when the feed was already at its first card. */
body{overscroll-behavior-y:none}
.feed,.stage-lyrics,.pl-list,.suggest,.modal,.sheet{overscroll-behavior:contain}

/* iOS zooms the page whenever a focused input is under 16px, and never zooms back out.
   One rule, and the single most noticeable difference on an iPhone. */
/* 16px is a FLOOR, not a size. `.search-big input` and `.sheet-search input` were deliberately 17px
   and `.code-inp` — the two-factor code field — 18px with wide letter-spacing; all three already
   cleared the floor, and sweeping them into this list quietly shrank them. Only fields that were
   below it are listed. */
.inp,.search input,textarea,select,input[type=text],
input[type=email],input[type=password],input[type=search],input[type=number],input[type=url]{font-size:16px}

/* Every touchable surface answers immediately. Fires only where there is no hover, and the
   prefers-reduced-motion block near the top already switches all of it off. */
@media (hover:none){
  .chip:active,.btn-accent:active,.btn-ghost:active,.icon-btn:active,.card-song:active,
  .strip-card:active,.song-row:active,.pl-tile:active,.artist-tile:active,.tabbar a:active,
  .menu-list a:active,.frail button:active,.frail a:active,.icb:active{
    transform:scale(.97);transition:transform .06s}
}

/* 44×44 minimum touch targets. The visual size is unchanged — an invisible pad does the work, so
   nothing in the layout moves.

   `.card-song .pl` and `.card-song .like` are NOT in the list below, and putting them there is the
   bug this comment exists to prevent repeating. Both are already `position:absolute` — the heart at
   the cover's top-left, the playlist button at its top-right — and both of those rules are
   `.card-song .pl` / `.card-song .like`, exactly the specificity this line had. Being later, this
   line won, and `relative` put the two buttons back into the card's flow: the heart dropped below
   the cover, the playlist button slid 10px off the LEFT edge of the card (a relative `right:10px`
   moves an element left), and the card grew 60px taller because two buttons that had been out of
   flow were suddenly in it. `position:absolute` is already a positioned value, so the ::after pad
   below anchors to them correctly with no help from here. Nothing needs to be added for them. */
.icon-btn.xs,.modal-close,.dock-btn,.song-row .p,.pl-songs .p{position:relative}
.icon-btn.xs::after,.card-song .pl::after,.card-song .like::after,.modal-close::after,
.dock-btn::after,.song-row .p::after,.pl-songs .p::after{
  content:"";position:absolute;top:50%;left:50%;width:44px;height:44px;transform:translate(-50%,-50%)}

/* The two scrubbers were 14px tall, which is not a drag target on a phone. The bar you SEE stays
   the same height; the bar you can GRAB is now 24px. */
/* The bar you SEE stays 14px; the bar you can GRAB is 24px — but as an overlay, not as height. As a
   height it mattered: .dock-rail is absolutely positioned across the top of the dock at z-index 2,
   so growing it to 24px laid a scrub surface over the top 16px of the album art and the first line
   of the title. Tapping the cover scrubbed the track. */
.dock-rail::after,.fbar .tr::after{content:"";position:absolute;left:0;right:0;top:-5px;height:24px}
.dock-rail,.fbar .tr{touch-action:none}
/* NOT `.dock-play{width:52px}` unqualified. That was here, below the phone rule, at the same
   specificity and outside any media query — so it beat `@media (max-width:899.98px){.dock-play{42px}}`
   at every width, and the play button rendered 52px inside a 64px dock bar: filling it edge to edge,
   its glow clipped, and larger than the 44px album art beside it. 46px already clears the 44px touch
   minimum, so only the phone size needed raising, and it is raised where it belongs. */
@media (min-width:900px){.dock-play{width:52px;height:52px}}
.chip,.btn-accent,.btn-ghost{min-height:44px}
.btn-accent.sm,.btn-ghost.sm{min-height:40px}

/* A bottom sheet with no handle does not look draggable. */
@media (max-width:639.98px){
  .modal::before{content:"";display:block;width:38px;height:4px;border-radius:2px;
    background:var(--line-2);margin:-8px auto 14px}
}

/* Notch safety on the sides, not just the bottom. */
.page,.hdr-in,.ftr-in{padding-inline:max(18px,env(safe-area-inset-left)) max(18px,env(safe-area-inset-right))}

/* The dock rail keeps a visible focus ring: it is role="slider" with tabindex="0", and "3px line
   becomes 5px line" was indistinguishable from hover. */
.dock-rail:focus-visible{outline:2px solid var(--acc);outline-offset:-2px;border-radius:4px}


/* ---- Work page: action rows ------------------------------------------------
   Both rows are full-width and share the spectrum's edges, so the block reads as one column rather
   than a centred cluster. width:100% is load-bearing: on phones .song-info is a column flex with
   align-items:center, which shrank these rows to their content and turned four cells into four tall
   squircles floating in the middle of the page.
   The cells are horizontal (icon beside label, not above it) — half the height, and the same pill
   shape as row 2, so the two rows belong to each other. */
.song-actions{display:flex;flex-direction:column;gap:8px;margin-top:14px;width:100%;align-self:stretch}
.sa-row{display:flex;gap:8px;align-items:stretch;width:100%}
.sa-row > *{flex:1 1 0;min-width:0}
.sa{display:flex;align-items:center;justify-content:center;gap:7px;
  padding:0 8px;border:0;background:var(--glass,rgba(127,127,127,.07));border-radius:var(--r-sm,14px);
  box-shadow:inset 0 0 0 1px var(--line);color:var(--text);
  font:600 13px/1 inherit;text-decoration:none;min-height:46px;cursor:pointer;
  transition:box-shadow .18s var(--ease),background .18s var(--ease),transform .06s}
.sa .ic{display:grid;place-items:center;width:20px;height:20px;flex:none}
.sa .ic svg{width:20px;height:20px}
.sa > span:last-child{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.sa > span:last-child:empty{display:none}
.sa:hover{box-shadow:inset 0 0 0 1px var(--line-2)}
.sa.on{color:#ff6b81}
/* Row 2: the cover invitation takes the space, the flag stays a square. */
.sa-more .btn-ghost{min-height:46px;justify-content:center;border-radius:var(--r-sm,14px)}
.sa-more .btn-ghost.icon-only{flex:0 0 46px;padding:0}
/* Paylaş carries no label — but it is NOT narrower for it. Beğen, yorum and paylaş are one row of
   identical cells (flex:1 1 0 above makes their width identical whatever is inside them, and
   align-items:stretch on the row makes their height identical); "Liste" is the only one with a word
   in it, so it is the only one that gets extra room. */
.sa.icon-only{padding:0;gap:0}
.sa-row > .sa-wide{flex:1.8 1 0}
@media (hover:none){.sa:active,.sa-more .btn-ghost:active{transform:scale(.98)}}

/* ---- Share sheet ----------------------------------------------------------- */
.share-sheet .share-link{display:flex;gap:8px;align-items:center;margin:14px 0 10px}
.share-sheet .share-link .inp{flex:1;min-width:0;font-size:14px}
.share-sheet .share-link .btn-accent{flex:none;gap:6px}
.share-native{width:100%;justify-content:center;margin-bottom:12px}
/* auto-fit rather than a fixed 4, so the five services sit on one row in a 360px modal and spread
   out on a desktop one instead of leaving a single orphan tile on a second row. */
.share-apps{display:grid;grid-template-columns:repeat(auto-fit,minmax(64px,1fr));gap:8px}
/* Each service carries its own colour in --b, and the colour appears where he asked for it: BEHIND
   the glyph. color-mix keeps one rule working in both themes — the same brand colour is mixed into
   whatever the page background happens to be, so the tint is light on light and deep on dark. */
.share-app{--b:var(--acc);
  display:flex;flex-direction:column;align-items:center;gap:8px;padding:12px 4px 11px;
  border-radius:var(--r-lg,16px);box-shadow:inset 0 0 0 1px var(--line);text-decoration:none;
  color:var(--text);font:600 11.5px/1 var(--body,inherit);background:var(--glass,rgba(127,127,127,.07));
  transition:box-shadow .18s var(--ease),background .18s var(--ease),transform .06s}
.share-app .ic{display:grid;place-items:center;width:42px;height:42px;border-radius:50%;
  color:var(--b);background:color-mix(in srgb,var(--b) 16%,transparent);
  box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--b) 26%,transparent)}
.share-app .ic svg{width:22px;height:22px}
.share-app > span:last-child{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}
.share-app:hover{background:color-mix(in srgb,var(--b) 10%,transparent);
  box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--b) 34%,transparent)}
.share-app.wa{--b:#25d366}
.share-app.tg{--b:#29a9eb}
.share-app.x{--b:var(--text)}
.share-app.fb{--b:#1877f2}
.share-app.mail{--b:#f0932b}
@media (hover:none){.share-app:active{transform:scale(.97)}}
/* Nobody pastes an <iframe> into anything from a phone. On a touch screen this dialog is only ever
   the fallback for a browser with no OS share sheet, and the embed block is dead weight there. */
@media (pointer:coarse){.share-embed{display:none}}
.share-embed{margin-top:14px;border-top:1px solid var(--line);padding-top:12px}
.share-embed summary{cursor:pointer;font-weight:600;display:flex;align-items:center;gap:8px;
  list-style:none;padding:6px 0;min-height:44px}
.share-embed summary::-webkit-details-marker{display:none}
.share-embed textarea{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12.5px;margin:8px 0}
.share-embed .emb-preview{margin-top:12px}

/* ---- Double-tap to like: the heart that flies off the sleeve ---------------- */
[data-dbl-like]{position:relative}
.dbl-heart{position:absolute;z-index:5;width:96px;height:96px;margin:-48px 0 0 -48px;
  pointer-events:none;color:#ff6b7f;filter:drop-shadow(0 8px 24px rgba(0,0,0,.45));
  animation:dblpop .72s var(--ease) forwards}
.dbl-heart svg{width:100%;height:100%}
@keyframes dblpop{
  0%{opacity:0;transform:scale(.35)}
  22%{opacity:1;transform:scale(1.12)}
  40%{transform:scale(.96)}
  100%{opacity:0;transform:scale(1.05) translateY(-26px)}
}
@media (prefers-reduced-motion:reduce){.dbl-heart{animation:none;opacity:0}}

/* ---- Imprint, contact form, admin inbox --------------------------------------------------- */
/* A definition list rather than paragraphs: an imprint is a set of labelled facts, and on a phone the
   label wants to sit above its value rather than beside it. */
.imprint{display:grid;grid-template-columns:auto 1fr;gap:6px 16px;margin:10px 0 4px;font-size:14.5px}
.imprint dt{color:var(--muted);font-weight:600}
.imprint dd{margin:0;color:var(--text)}
@media (max-width:520px){.imprint{grid-template-columns:1fr;gap:2px}.imprint dd{margin-bottom:8px}}

.contact-form{display:flex;flex-direction:column;gap:10px;margin-top:6px}
.contact-form textarea{resize:vertical;min-height:120px}
/* The honeypot. Not display:none — some bots skip what is display:none, and a few browsers skip it for
   autofill too; off-screen with no height is both invisible to a person and irresistible to a script. */
.hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}

.msg{gap:10px}
.msg-new{box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--acc) 45%,transparent)}
.msg-head{display:flex;justify-content:space-between;align-items:baseline;gap:12px;flex-wrap:wrap}
/* Somebody else's text, wrapped hard: a message with a 400-character "word" in it must not widen the page. */
.msg-body{white-space:pre-wrap;overflow-wrap:anywhere;background:var(--bg3);border-radius:var(--r-sm);padding:12px;margin:2px 0}
