/* attract screen: title, subtitle and the rule are defined ONCE in
   framework/kiosk.css now. The override that sat here made this station
   look different from its five neighbours for no reason anyone chose.
   Only the Devanagari line-height tuning stays local, because the strings
   differ. See the attract block in kiosk.css. */
/* Sounds of the Loom (station 5) app-specific styles.
   Portrait 1080x1920 (22" ViewSonic TD2230 rotated).
   Shared design system lives in ../../framework/kiosk.css. */

@media (orientation: portrait) {
  body { font-size: clamp(15px, 1.85vw, 21px); }
}

.hide { display: none !important; }

/* ---------- hidden layers must not swallow touches ----------
   The framework sets .screen { pointer-events: none } for inactive screens,
   but it also sets `button, .touchable { pointer-events: auto }`, which
   re-enables every control inside a screen that is invisible. Without this
   block a tap meant for one screen lands on an invisible control of another,
   and the very first wake tap drops the visitor straight into the audio
   player. Same story for the hidden screensaver: its .pulse pill is
   .touchable, so it keeps eating taps in a dead zone over the live screen.
   Reported upstream for a central fix; this is the local guard. */
.screen:not(.active), .screen:not(.active) *,
#saver.hidden, #saver.hidden * { pointer-events: none !important; }

/* The local film-player seek-bar guard that used to live here has been
   removed: the border-box trap it worked around is fixed centrally, and
   kiosk.css now builds .seek as a 64px target with an 8px visible rail. */

/* Long words from the manifest must wrap, never spill off the panel. */
.track-title, .track-dur, .strip-title,
#np-title, .np-region, .empty .body,
.list-head h2, .list-line,
.topbar .title { overflow-wrap: anywhere; word-break: break-word; }

/* The gallery ground, as three numbers. It is the same colour as --bg in
   kiosk.css and has to be kept in step with it; it is written out here because
   a scrim needs an ALPHA of the ground, and a custom property holding a
   finished colour cannot be given one. Relative colour syntax would do it in
   one line, but the panel runs whatever Chromium is on the machine and this
   works everywhere. */
:root { --ground: 22, 19, 15; }

/* ---------- screensaver ----------
   The venue eyebrow ("VISHWAKARMA GALLERY") is gone: it told a visitor
   standing in the room the name of the room, and its height is spent instead
   on carrying the title and the action in both languages at once. */
#saver h1 { margin-top: .2em; }

/* The stacking, the Devanagari faces and the never-swap behaviour of
   data-i18n-both all belong to the framework; nothing about them is repeated
   here. The inline markup in index.html mirrors what the framework builds so
   the poster still reads correctly before the first applyLang. */

/* ---------- the recordings (this is the home screen) ----------
   Portrait panel, wall mounted: the reachable, readable band is roughly 12%
   to 88% of the 1920px height. The heading used to start at 18vh, which spent
   345px of a 1920px panel on nothing before the station had said its name, and
   the rows then ended with a further dead strip below them. Measured on the
   real screen, the ink ran 345..1646 and the top eighteenth of the panel was
   empty. The heading now begins at 6vh and the rows are taller, so the
   composition runs 157..1638, and what is left below it is the band the
   now-playing strip floats in rather than nothing at all.

   The heading block is given a FIXED height and its content centred inside it,
   which is the one thing holding the whole screen together across type steps.
   A heading that grew with the type step pushed all five rows down with it,
   and at the large step in Hindi it pushed the last row's tappable centre
   under the now-playing strip: measured, the centre landed at 1564 against a
   strip top of 1562, so with music playing the fifth recording could not be
   tapped at all. Pinned, the rows begin at 326 at every step in both
   languages, the last centre stays at 1514, and the worst case clears the
   strip by 48px. The heading itself still scales; it grows into the air the
   box already reserves for it, and only a hand-edited title long enough to
   fill 300px would move anything.

   The 6vh floor keeps the title clear of the framework's fixed pills, which
   both end by 86px. Its lowest measured top is 129px, in Hindi at large. */
.list-head { min-height: 300px; padding: 6vh 1.6em 0; text-align: center;
             display: flex; flex-direction: column; justify-content: center; }
/* Hierarchy by face and weight, not by size alone. The station name takes the
   display face at 400, the row titles the text serif at 600: big-and-light
   against small-and-solid reads as title against label from across a gallery
   in a way that two sizes of one weight does not. The ratio widens too, from
   1.4x over the row title to 2x. */
.list-head h2 { font-family: var(--display); font-weight: 400;
                font-size: 3.1em; line-height: 1.1; letter-spacing: .008em; }
.list-line { margin-top: .5em; font-size: 1.05em; }
/* app.js sets .is-empty when there is nothing to play: the station keeps its
   name and drops a promise it cannot keep. */
#screen-list.is-empty .list-line { display: none; }

/* The bottom padding used to be a flat 19vh, to hold the last row clear of the
   now-playing strip that floats over the foot of the panel. But the strip is
   only up once a recording is playing; on the resting list, which is what
   almost every visitor sees, those 19vh were ~360px of dead panel below the
   last row and were the main reason the screen looked half empty from across
   the gallery. A small, constant bottom margin replaces it. Today's five rows
   sit entirely above the strip's band, so the strip floats clear of every row's
   tappable centre; and a longer, scrolling playlist runs its last row down
   BELOW the strip at full scroll (its centre clear of the strip either way),
   which is what testing/test_music_rhythm.py phase D3 verifies. */
.tracks { flex: 1; overflow-y: auto; overscroll-behavior: contain;
          display: flex; flex-direction: column; gap: 18px;
          padding: 26px 1.4em 3vh; pointer-events: auto; }
.tracks::-webkit-scrollbar { width: 0; }

/* ---------- a row IS its cloth ----------
   The five delivered "-art.svg" files are one identical placeholder, so five
   different recordings drew five identical 160px grey squares, each in its own
   bordered box with the words parked alongside. That is what "too basic with
   big buttons" was looking at: default card, default thumbnail, default
   caption bar.

   A row is now a single surface. app.js lays authentic woven cloth on the
   button itself (framework/weave-swatch.js, the same ten pattern functions the
   idle screen weaves), and the type sits on it. No thumbnail, no inner box, no
   border. The only geometry left is a small radius and a shadow, so a row
   reads as a piece of cloth lying on the panel.

   The vertical rhythm here is in px, not em, on purpose. The type-size control
   must change the WORDS on this screen and not the composition: if the rows
   grew by a quarter at the large step, five of them would no longer fit the
   panel and the last one would slide under the now-playing strip. Measured at
   1080x1920, the five rows run 326..1638 at every type step in both languages,
   and the last row's tappable centre sits at 1514 against a strip that begins
   at 1562 in its tallest state (Hindi, large). */
.track {
  position: relative; isolation: isolate;
  display: flex; align-items: center; text-align: left;
  min-height: 248px; padding: 1.3em 1.7em;
  border: 0; border-radius: 6px;
  background: var(--bg-card) center / cover no-repeat;
  color: var(--ink); font-family: var(--sans); font-size: 1em; pointer-events: auto;
  box-shadow: 0 10px 26px rgba(0,0,0,.42);
}
/* The scrim. Cloth is high-frequency and mid-value, so white type straight on
   it is legible on one bolt and not the next. This is heavy where the words
   are and thins out to the right, so the cloth is still cloth in the half of
   the row that is picture rather than caption. The soft round pool at the far
   right does the same job for the play glyph without drawing a button under
   it: padding and shade are invisible, a bordered circle is not. */
.track::before {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  pointer-events: none;
  /* The first stop pair covers the whole text column and does not thin out
     inside it: measured on the brightest weft in the set, .95 holds the row's
     dimmest line (the length, in --ink-dim) at 5:1, and anything lighter put it
     under AA on one bolt while leaving it fine on the next four. The fall to
     .08 happens entirely to the right of the words, so roughly the last 400px
     of every row is cloth at very nearly full strength. */
  background:
    linear-gradient(93deg,
                    rgba(var(--ground), .965) 0%, rgba(var(--ground), .95) 40%,
                    rgba(var(--ground), .5) 66%, rgba(var(--ground), .08) 100%);
}
/* Pressed state without a border to change colour: a hairline of gold drawn
   inside the edge, plus the same small scale the rest of the gallery uses. */
.track:active { transform: scale(.99);
                box-shadow: inset 0 0 0 2px var(--gold), 0 10px 26px rgba(0,0,0,.42); }

/* The words are held to the left of the row, inside the part of the scrim that
   never thins out. That is a legibility rule, not a margin: a long title added
   on site wraps within this column and stays on the dark ground, instead of
   running out across the bright half of the cloth where nothing would hold it.
   The remaining ~58% of every row is picture. */
.track-info { position: relative; z-index: 1;
              flex: 1; min-width: 0; max-width: 42%;
              display: flex; flex-direction: column; gap: .3em; }
/* The running number, in the framework's eyebrow: a gold tracked cap line
   above the title, so the title no longer has to share its line with anything
   and the length below it no longer has to carry the row on its own. */
.track-no { line-height: 1; }
.track-title { font-family: var(--serif); font-weight: 600; font-size: 1.55em;
               line-height: 1.28; color: var(--ink);
               display: flex; align-items: center; gap: .5em; flex-wrap: wrap; }
.track-badge { font-family: var(--sans); font-size: .55em; letter-spacing: .12em;
               color: var(--gold); border: 1px solid var(--gold); border-radius: 999px;
               padding: .25em .8em; }
/* The play mark used to float at the far right edge of the row. On cloth that
   is where the scrim has thinned out to almost nothing, and two of the ten
   palettes carry a yellow weft within a shade of --gold, so the glyph
   disappeared into its own background on those bolts. Shadows behind it did
   not fix that and showed as a smudge. It now reads as part of the caption,
   "play, twenty-four seconds", on the same protected ground as the title. */
.track-meta { display: flex; align-items: baseline; gap: .55em; }
.track-dur { color: var(--ink-dim); font-weight: 300; }
.track-go { color: var(--gold); font-size: 1.15em; flex: none; line-height: 1; }

/* A track whose media file has not been delivered yet keeps a permanent,
   visible "Coming soon" state instead of silently doing nothing. It is the
   only thing standing between a visitor and a tap that produces nothing, so
   it is a solid badge rather than the faintest text on the row. */
.track.unavailable { opacity: .62; }
.track.unavailable .track-go { color: var(--ink-faint); }
.track-badge.soon { background: var(--ink-dim); color: var(--bg);
                    border-color: var(--ink-dim); letter-spacing: .04em; }

.empty { text-align: center; padding: 4em 2em; }
.empty .body { font-size: 1.15em; }

/* now-playing strip, held inside the reachable band rather than jammed
   against the bottom edge of a wall-mounted panel */
.np-strip {
  position: absolute; left: 1.4em; right: 1.4em; bottom: 12.5vh; z-index: 30;
  display: flex; align-items: center; gap: 1em; text-align: left;
  min-height: 88px; padding: .7em 1em;
  background: var(--bg-raised); border: 1.5px solid var(--gold); border-radius: var(--radius);
  color: var(--ink); font-family: var(--sans); font-size: 1em; pointer-events: auto;
  box-shadow: 0 8px 30px rgba(0,0,0,.55);
}
.np-strip:active { transform: scale(.985); }
/* The strip keeps its solid shell and its gold edge: it floats over rows of
   cloth and has to read as chrome, not as a sixth recording. Its chip carries
   the same bolt as the row that is playing. */
#strip-art { width: 68px; height: 68px; border-radius: 4px; object-fit: cover;
             flex: none; background: var(--bg-card); }
.strip-info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: .15em; }
/* No ellipsis anywhere in this app: a title that will not fit on one line
   wraps onto a second and the strip grows upward, which costs nothing. */
.strip-title { font-family: var(--serif); font-size: 1.15em; }
.strip-state { color: var(--gold); font-size: 1.2em; flex: none; padding: 0 .4em; }

/* ---------- now playing (dedicated audio player) ---------- */
/* The NOW PLAYING caption used to be right-aligned, which put its ink exactly
   under the framework's fixed top-right Home pill (measured overlap at
   1080x1920: label ink x 878-1048 vs pill x 908-1058). The pill deliberately
   keeps the same spot in every app of the gallery, so the app's own decorative
   caption moves instead: centred between the back button and the pill. */
#screen-player .np-label { text-align: center; letter-spacing: .18em; }

/* The artwork is decoration and yields to words, rather than words being cut
   to protect the artwork. 38vh is what it takes when there is room; it is
   shrinkable (flex-shrink 1, min-height 0) so that a runaway manifest title
   pushes the picture smaller instead of pushing the transport off the bottom
   of the panel. That used to be the job of a two-line clamp on the title, and
   a clamp is a truncation a visitor cannot undo. Verified with the 480-char
   unbroken title in testing/test_music_rhythm.py phase C. */
/* The basis stays 38vh and the shrink stays 1, so the sentence above still
   holds: a runaway title takes the picture's height, not the transport's. What
   is new is flex-grow, and a ceiling of 82vw on the wrap rather than only on
   the image. The artwork was a 706px square on a 1920px panel with 320px of
   nothing under the volume slider; it now grows into that space and stops at
   the panel's own width, so it stays square instead of stretching into a
   portrait crop, and the screen reads as one picture with its controls under
   it rather than as a small picture floating in a tall empty frame. */
.art-wrap { flex: 1 1 38vh; min-height: 0; max-height: 82vw; position: relative;
            display: flex; align-items: center; justify-content: center;
            padding: .6em 1.6em; }
/* The controls never give ground: they are the reason the screen exists. */
.np-meta, .np-progress, .transport, .vol-row { flex: none; }
/* The border has gone with the placeholder it used to frame. The artwork is a
   real square of cloth now (or a photograph, once one is delivered), and a
   picture does not need a line drawn round it to be a picture; a shadow lifts
   it off the panel instead. The radius comes down to match the rows. */
#np-art { height: 100%; max-width: 82vw; aspect-ratio: 1 / 1; object-fit: cover;
          border-radius: 8px; border: 0; background: var(--bg-card);
          box-shadow: 0 16px 44px rgba(0,0,0,.5); }
.missing { position: absolute; inset: .6em 1.6em; border-radius: 18px;
           display: flex; flex-direction: column; align-items: center; justify-content: center;
           gap: .7em; text-align: center; padding: 1.5em;
           background: rgba(22,19,15,.92); border: 1px solid #3a332a; }

/* The registrar's note that used to sit under the title here is gone with the
   row description; the line clamps went with it. Nothing in this app now
   truncates text a visitor has no way to expand. The five delivered titles
   are three or four words, and with the note deleted the player has roughly
   370px of slack below the volume row, so a longer one added on site wraps
   rather than pushing the transport anywhere. */
/* The last line of defence against a hand-edit on site. The five delivered
   titles are three or four words and never come near this, but the manifest is
   edited in the gallery for the life of the exhibition, and a pasted paragraph
   in the title field must not push the play button off the bottom of the
   panel. Capped and scrollable rather than clamped: every word is still
   reachable, which an ellipsis would not be. */
.np-meta { text-align: center; padding: 1em 2em .4em;
           max-height: 30vh; overflow-y: auto; overscroll-behavior: contain;
           touch-action: pan-y; }
.np-meta::-webkit-scrollbar { width: 0; }
/* Same hierarchy as the list: eyebrow, then the title in the display face at
   400, then the metadata. The player title used to sit at 2.05em serif 500,
   which is a hair away from the row caption it came from, so a visitor
   arriving here could not tell they had moved up a level. */
.np-index { line-height: 1; margin-bottom: .5em; }
.np-meta h2 { color: var(--ink); font-family: var(--display); font-weight: 400;
              font-size: 2.9em; line-height: 1.1; letter-spacing: .008em; }
.np-region { margin-top: .5em; letter-spacing: .16em; }

/* The lower stack is given the rest of the height the bigger artwork did not
   take, rather than leaving it in one dead band under the volume slider. The
   controls are unchanged in size; only the air between them has grown. */
.np-progress { padding: 1.4em 2em 0; }
/* content-box is deliberate: the framework's global `* { box-sizing: border-box }`
   would collapse a 12px-tall bar with 24px padding to a 0px-tall track, so both
   the groove and the gold fill would render invisible. 12 + 26 + 26 = 64px hit area. */
/* v2 14 Aug: hit area measured 60px zero-flag (12 + 24 + 24), under the 64px touch
   floor; padding 24px raised to 26px, measured after: 64px, groove still 12px. */
.np-bar { box-sizing: content-box; height: 12px; border-radius: 6px; background: #3a332a;
          position: relative; pointer-events: auto; padding: 26px 0;
          background-clip: content-box; }
.np-fill { position: absolute; top: 26px; bottom: 26px; left: 0; width: 0%;
           background: var(--gold); border-radius: 6px; }
.np-times { display: flex; justify-content: space-between; margin-top: .1em; font-size: .95em; }

.transport { display: flex; align-items: center; justify-content: center;
             gap: 2.4em; padding: 1.7em 0 1em; }
.tbtn { width: 96px; height: 96px; border-radius: 50%;
        border: 1px solid rgba(201,162,75,.55); background: transparent; color: var(--gold);
        font-size: 1.5em; display: flex; align-items: center; justify-content: center;
        pointer-events: auto; }
.tbtn:active { background: var(--gold); color: var(--bg); }
/* The one control that carries the screen keeps the full-strength ring and the
   faint gold wash; prev and next step back to a hairline. Three identical gold
   circles made the transport read as three equal decisions, which it is not. */
.tbtn.big { width: 136px; height: 136px; font-size: 2.3em;
            border: 1.5px solid var(--gold); background: rgba(201,162,75,.1); }

.vol-row { display: flex; align-items: center; gap: 1.2em; padding: 1.2em 2.4em 3em; }
.vol-icon { flex: none; color: var(--ink-faint); }
.vol-icon.loud { color: var(--gold); }
#vol { flex: 1; -webkit-appearance: none; appearance: none; height: 64px;
       background: transparent; pointer-events: auto; }
#vol::-webkit-slider-runnable-track { height: 10px; border-radius: 5px; background: #3a332a; }
#vol::-webkit-slider-thumb { -webkit-appearance: none; width: 54px; height: 54px;
       border-radius: 50%; background: var(--gold); border: 4px solid var(--bg);
       margin-top: -22px; box-shadow: 0 2px 10px rgba(0,0,0,.5); }
#vol::-moz-range-track { height: 10px; border-radius: 5px; background: #3a332a; }
#vol::-moz-range-thumb { width: 54px; height: 54px; border-radius: 50%;
       background: var(--gold); border: 4px solid var(--bg); }

/* ---------- toast ---------- */
#toast { position: fixed; left: 50%; bottom: 6vh; transform: translate(-50%, 24px);
         z-index: 300; max-width: 86vw; padding: 1em 1.8em; border-radius: 999px;
         background: var(--bg-raised); border: 1.5px solid var(--gold); color: var(--ink);
         font-family: var(--serif); font-size: 1.05em; text-align: center;
         opacity: 0; transition: opacity .35s, transform .35s; pointer-events: none; }
#toast.show { opacity: 1; transform: translate(-50%, 0); }


/* ── escape & abuse hardening (Aug 2026 abuse pass) ───────────────────────────
   Gestures must stay inside the app. `touch-action: manipulation` (kiosk.css)
   still lets Chromium claim a two-finger pinch as a browser zoom: reproduced at
   visualViewport.scale = 4 in testing/test_escape.py, and NOT stopped by the
   --disable-pinch launch flag. Pinning the full-screen layers to
   `touch-action: none` and re-granting only the axis each scroller needs is what
   actually holds. Chrome stops the touch-action walk at the scroller, so
   `none` on the .screen does not disable `pan-y` inside it (measured).
   `overscroll-behavior: none` stops the two-finger overscroll back/forward
   navigation without relying on --overscroll-history-navigation=0. */
html, body { overscroll-behavior: none; }
img, video, svg { -webkit-user-drag: none; user-drag: none; }
#saver, .screen, .player { touch-action: none; }
.tracks { touch-action: pan-y; }
input[type=range] { touch-action: none; }   /* the slider drags itself */

/* ---------- arriving-screen guard ----------
   The incoming screen gets pointer-events the instant .active lands, but it is
   still invisible for the first part of the .45s fade. A visitor who tapped
   twice in quick succession had the second tap land on the track row that had
   just appeared under their finger and was thrown into the player, past a list
   they never saw. That now matters more, not less: the first touch on the
   attract screen lands directly on the recordings, so the wake tap itself has
   live rows under it, and app.js arms this guard on wake as well as on every
   navigation. app.js marks a just-arrived screen .arriving for 500 ms; here its
   controls are deadened and dimmed, so the pause reads as the screen settling
   in rather than as a tap that did nothing. */
.screen.arriving .touchable,
.screen.arriving .btn,
.screen.arriving .track { pointer-events: none; opacity: .5; }
.screen .touchable, .screen .btn { transition: opacity .3s ease; }

/* ==========================================================================
   GALLERY LEGIBILITY AUDIT (app-local corrections)
   Panel: 22" 1080x1920 portrait, visitors standing 40-80 cm away.
   ========================================================================== */

/* [VIS-1] Only `body` was being re-based for portrait, so the ROOT font stayed
   at kiosk.css's clamp floor of 14px (1.2vw of 1080 = 12.96px, clamped). Any
   rem-sized rule, the framework's own `video::cue` sizing among them, was
   therefore computed against 14px instead of 20px. Re-base html as well, the
   way apps/archival-deep-dive/app.css already does. Reported upstream: the
   framework clamp should use vmax, not vw, so portrait panels are not starved. */
@media (orientation: portrait) {
  html { font-size: clamp(15px, 1.85vw, 21px); }
}

/* [VIS-2] The "not ready yet" card sits over the artwork at 92% alpha, and
   the artwork bled through it. The English title baked into those SVGs has
   since been deleted (it was the loudest English on a Hindi screen), but the
   woven ground still shows through and still muddies the one message a
   visitor needs to read. Make the panel opaque. */
.missing { background: var(--bg); }

/* [VIS-3] Gallery reading floor is ~18px rendered, and --ink-faint measures
   3.15:1 on --bg-card / 3.84:1 on --bg (AA body needs 4.5:1). These are all
   app-owned text runs.

   The secondary "meta" lines (the row's length, and the player's region and
   clock) now carry a hard 22px floor, so they stay legible to a visitor
   standing at the panel rather than only to one who leans in. 1.05em / .95em
   still compute below 22px on this portrait panel, so max() pins them to the
   floor here while letting them scale up on any larger display. */
.track-dur    { font-size: max(22px, 1.05em); }
.track-badge  { font-size: .7em; }
.np-region    { font-size: max(22px, .95em); color: var(--ink-dim); }
.np-times     { font-size: max(22px, .95em); color: var(--ink-dim); }
#screen-player .np-label { font-size: .95em; color: var(--ink-dim); }
/* The strip's "Now playing" line: .9em computed 17.982px at 1080x1920 on the
   zero-flag portrait boot of 14 Aug, a hair under the project's 18px text
   floor (the .999 comes from the vw-derived root size at 1080 wide, so the
   real panel gets the same number). Same max() pattern as the meta lines
   above: pinned to the floor here, free to scale on a larger display. */
.strip-info .caption { font-size: max(18px, .9em); color: var(--ink-dim); }

/* [VIS-4] The madder-ringed YouTube glyph that this note used to correct has
   gone with the streaming stub it sat on. */

/* ==========================================================================
   BILINGUAL PASS (clearing the framework's fixed pills, and Devanagari)
   ========================================================================== */

/* [I18N-1] The framework now fixes TWO pills over every screen: the language
   toggle top-left (measured 20..224 x 20..85 at 1080x1920, z 56) and the Home
   pill top-right (909..1058 x 22..86, z 55). This app's .topbar sat in exactly
   that band: "‹ Home" measured 32..182 x 22..86, completely underneath the
   language toggle, and therefore not merely overlapping but UNTAPPABLE. The
   screen title started at x 206 and ran under the toggle too.

   Fixed by dropping the topbar below the pill band rather than by squeezing it
   between the two pills. It now clears it by a wide margin, for two reasons:
   the toggle grows to a 64px target framework-wide, and on a wall-mounted
   portrait panel the app's own chrome belongs inside the reachable band
   (roughly 12% to 88% of the height) rather than above it. The "‹ All
   recordings" control is chrome, so it stays at the top of that band, just
   clear of the two pills; the list screen's own heading is content, not chrome,
   and now sits lower, at eye level, so it is deliberately not on this line.

   Wound back from 12vh to 8vh. 12vh was 230px of nothing above a control that
   only has to clear a pill band ending at 86px, and the whole player paid for
   it at the other end: at the large type step the volume slider was hard on
   the bottom edge of the panel with the artwork being squeezed to make room.
   8vh is 154px, so the back control still clears the pills by 68px, and the
   76px goes to the foot of the screen, where the measured bottom margin is now
   165px at the medium step and 76px at the large one. */
.topbar { padding-top: 8vh; }

/* The back control loses its box. It was a bordered, filled-on-press lozenge,
   which is a lot of chrome for the one thing on this screen that is not the
   music. Gold ink with generous padding says "touch me" in this palette just
   as clearly as an outline does, and the target is unchanged: padding is
   invisible, so the box measures ~82px tall at the medium step and ~73px at
   the small one, both well over the 60px gallery floor. */
#btn-player-back { border-color: transparent; background: transparent;
                   color: var(--gold); font-size: 1.2em;
                   padding: 1em 1.2em; border-radius: 8px; }
#btn-player-back:active { background: rgba(201,162,75,.16); color: var(--gold); }

/* Measured at 1080x1920 after the eye-level move: the recordings heading starts
   at y=346, the player's back button at y=230, the five rows run from y=470 to
   y=1646 (filling the band), and the now-playing strip runs 1592..1680. Nothing
   this app draws sits above 12% or spills past the foot of the panel; the only
   things in the corners are the framework's own two pills, which keep the same
   spot in all six apps. */

/* [I18N-1b] The local raise of the language toggle above the screensaver has
   been removed: the framework now carries the toggle at z 110, above both the
   idle screen and the film player, so all six apps get the same answer. */

/* [I18N-2] Devanagari has no case and no small-caps tradition, and it hangs
   its matras above and below the line. Latin tracking of .1–.18em, which reads
   as quiet gallery capitals in Lato, pulls Devanagari clusters apart into
   loose beads. Wind it back to a hair for Hindi and give the taller script the
   leading it needs so ascenders and matras do not touch the line above. */
html[lang="hi"] .caption,
html[lang="hi"] .np-region,
html[lang="hi"] .track-badge,
html[lang="hi"] #screen-player .np-label { letter-spacing: .02em; }
html[lang="hi"] .np-region { letter-spacing: .06em; }   /* still reads as a label */
html[lang="hi"] body { line-height: 1.5; }
html[lang="hi"] h1, html[lang="hi"] h2, html[lang="hi"] h3 { line-height: 1.35; }

/* [I18N-3] The line clamps this note used to tune have been deleted rather
   than re-tuned: nothing in this app truncates any more.

   The one leading correction still needed is on the attract poster. Its two
   halves are painted at the same time, so the Devanagari half cannot pick up
   the html[lang="hi"] leading above and takes the Latin display leading of
   1.15 instead. Measured at 1080x1920: the poster title's Hindi ink box is
   75px inside a 60px line box, and its matras therefore reach up towards the
   Latin line sitting directly on top of it. 1.45 is the ratio at which the
   line box finally contains the ink for both the title and the action.
   Scoped to #saver, since that is the only place in this app that shows both
   scripts at once; everywhere else the toggle switches. */
#saver [data-i18n-both] > [lang="hi"] { line-height: 1.45; }

/* [I18N-4] The Hindi "Coming soon" badge ("जल्द आ रहा है", the one badge string
   locked across all six stations) is longer than the English; the badge sits in
   a wrapping flex row, so it drops to its own line rather than stretching the
   row. Keep it from also growing taller than the title it sits beside. */
html[lang="hi"] .track-badge { font-size: .62em; padding: .3em .7em; }

/* ==== HH LOGO STYLE, PROPAGATED FROM THE QUIZ (Vivek 16 Aug 21:52) =========
   The quiz's approved logo treatment, applied verbatim (see apps/quiz/app.css
   revisions 11-14 for the full reasoning):
   1. Attract screen: stacked lockup top right - the REAL standalone emblem
      (hh-emblem.png, two-tone, rendered directly, never a single-ink mask)
      over "Handloom Haat" in the credit's voice, upright, small caps.
   2. The framework's horizontal .saver-mark img is hidden; the lockup stands
      in its place.
   3. In-app screens: NO top-right HH mark at all; the framework's bottom-left
      .wm-hh watermark (untouched) is the only in-app HH presence.
   FRAMEWORK CANDIDATE: this block is now duplicated across all six apps and
   belongs in kiosk.css/weave-saver.js on the next framework pass. */
.saver-mark img { display: none; }
.brand-watermarks .wm-hh-top { display: none; }
.saver-hh-row {
  position: absolute; z-index: 4; pointer-events: none;
  right: 1.4em; top: 1.1em;
  display: flex; flex-direction: column; align-items: center; gap: .35em;
}
.saver-hh-sym {
  height: calc(max(18px, .46em) * 2.84);
  width:  calc(max(18px, .46em) * 2.84 * 1.0117);
  background: url(../../framework/assets/hh-emblem.png) center / contain no-repeat;
  filter: drop-shadow(0 1px 4px rgba(8,6,4,.98))
          drop-shadow(0 0 10px rgba(8,6,4,.9))
          drop-shadow(0 0 22px rgba(8,6,4,.7));
}
.saver-hh-word {
  font-family: var(--sans); font-weight: 400; font-style: normal;
  font-size: max(18px, .46em);
  letter-spacing: .06em; text-transform: capitalize;
  font-variant-caps: small-caps; font-variant: small-caps;
  color: var(--ink);
  text-shadow: 0 0 3px rgba(8,6,4,.98), 0 1px 5px rgba(8,6,4,.98),
               0 0 18px rgba(8,6,4,.92), 0 0 34px rgba(8,6,4,.75);
}

/* v2.2.1, 17 Aug, VIVEK'S ITEM 3: "logos OUT of the weave and motif areas
   entirely", read together with his item 4, the khadi border bands. The marks
   have MOVED rather than been deleted: framework/weave-saver.js now weaves a
   khadi border between weave sections carrying one institution's emblem
   repeated saree-border style, cycling per border. So the attract screen's
   top-right lockup is retired here, because the identity it carried is now IN
   the cloth where he asked for it, and leaving both would show the same marks
   twice on one screen.
   HIDDEN, NOT DELETED, and deliberately so: this block is Vivek-approved work
   from 16 Aug 21:52 and the rules below it (.saver-hh-sym/-word geometry) are
   the record of how that lockup was built. One line restores it if he wants it
   back after seeing the borders. Scope: this is the ONLY change made in these
   five directories, per Bob's 11:07 extension.
   NOT TOUCHED: the framework's bottom-left .wm-hh watermark, and the quiz's
   own bottom-right cluster, which is separately Vivek-approved. */
.saver-hh-row { display: none; }
