/* 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. */
/* Passageway Interactive Screen app-specific styles.
   Base tokens, cards, buttons, player and saver come from ../../framework/kiosk.css */

/* ---------- inactive screens must be inert ----------
   kiosk.css sets `.screen { pointer-events: none }`, but pointer-events is not
   inherited-and-locked: any descendant that re-enables it (framework .grid, .card,
   .btn, .pbtn, .chip, .touchable, plus our own .strip) becomes hit-testable again
   even while its screen is invisible. Because #topic follows #home in the DOM at the
   same stacking level, the hidden #strip and #topic-back sat on top of the whole home
   screen and swallowed every tap: no topic card could be opened at all. Kill
   hit-testing on inactive screens outright, and drop them out of the hit-test tree
   entirely once the fade-out has finished. */
.screen { visibility: hidden; transition: opacity .45s ease, visibility 0s linear .45s; }
.screen.active { visibility: visible; transition: opacity .45s ease, visibility 0s linear 0s; }
.screen:not(.active), .screen:not(.active) * { pointer-events: none !important; }

/* ---------- gestures stay inside the app ----------
   Nothing here scrolls except the topic grid (vertically) and the media strip
   (horizontally). Without an explicit touch-action the browser claims every drag as a
   scroll: it fires pointercancel (so the image-viewer swipe never navigates) and an
   overscrolled horizontal drag becomes a history back/forward gesture, which walks the
   kiosk off the app to a blank page. kiosk-session.sh passes
   --overscroll-history-navigation=0, but the app must not depend on a launch flag. */
html, body { overscroll-behavior: none; }
#saver, .screen, .modal, .player { touch-action: none; }
#home .grid { touch-action: pan-y; }
.strip { touch-action: pan-x; }

/* ---------- screensaver ----------
   The attract screen is bilingual by construction (D1): the framework paints
   the English and the Hindi of each line, stacked, both always on screen. It
   also names the Devanagari face on the hi span, because that span is visible
   while html[lang] is still "en" and the html[lang="hi"] rules cannot reach it.
   Nothing app-local is needed for either of those. What IS needed is leading:
   Devanagari's ink runs from the top matra to the descending vowel sign, so a
   1.15/1.25 display line box is not tall enough for it whatever the toggle
   says. The Hindi pass at the foot of this file only fires on html[lang="hi"],
   which is the wrong condition for a line that is always on screen. */
/* the framework's 1.15 display leading is tighter than EB Garamond's own ink
   (measured 149px of ink in a 147px two-line box) */
#saver h1 { line-height: 1.25; }
#saver h1 > [lang="hi"], #saver .pulse > [lang="hi"] { line-height: 1.55; }
#saver .saver-sub > [lang="hi"] { line-height: 1.6; }

/* ---------- home: four woven panels ----------
   The settings cluster is fixed at top-left and the framework measures it for
   us, republishing --kiosk-chrome-bottom on every language and type-size
   change. The header clears it VERTICALLY rather than being shoved sideways,
   which keeps the title, the rule and the four cards on one left edge. Never a
   literal: the cluster is ~427px wide in English and wider once the Devanagari
   faces load, and hardcoding either number is how the old 240px broke. */
#home .hero { padding: calc(var(--kiosk-chrome-bottom, 108px) + .5em) 2.6em 0; }
/* Same measurement the screensaver headline needed, for the same reason: the
   framework's display leading is tighter than EB Garamond's own ink, so the
   descenders of "Living Traditions of the Loom" sat 5px outside their own line
   box (77px of ink in a 72px box, measured at 1920x1080). Nothing was clipped,
   because overflow is visible, but ink outside its box is ink in the element
   above it, and it is a real clip the moment anyone adds one. */
#home .hero h1 { line-height: 1.24; }
#home .weave-rule { margin: .85em 0 0; }
/* The old layout centred a short header and four small cards as one group and
   left the top and bottom thirds of a 1920x1080 panel empty. Four themes on a
   screen this size should be four full-height hangings, so the grid takes ALL
   the space under the header (flex: 1) and grid-auto-rows: 1fr makes the row
   fill it. A visitor reads this from a metre away: the cloth is the thing they
   see first and the card is as tall as the wall allows.
   The grid can still scroll inside itself if a hand-edited manifest ever grows
   past one screenful, which is what keeps a fifth theme from breaking it. */
#home { justify-content: flex-start; }
#home .grid { grid-template-columns: repeat(4, 1fr); grid-auto-rows: 1fr;
              gap: 1.05em; padding: 1.05em 2.6em 2.1em; flex: 1; align-content: stretch; }

/* THE CLOTH IS THE CARD.
   Not a frame with a picture in it and a filled caption bar bolted underneath:
   the weave runs to all four edges and the label sits on it, the way a textile
   is captioned on a gallery wall. The band that makes the label legible is
   baked into the weave by the renderer (scrim: 'bottom'), so there is no
   second surface and no hard edge across the card.
   .meta is pushed to the foot by margin-top: auto, and the cloth is absolutely
   positioned behind it, so a long title grows upwards into the cloth instead
   of pushing the card taller than its cell. */
.topic-card { position: relative; }
.topic-card .thumb { position: absolute; inset: 0; z-index: 0;
                     width: 100%; height: 100%; aspect-ratio: auto;
                     object-fit: cover; background: transparent; }
.topic-card .meta { position: relative; z-index: 1; margin-top: auto;
                    padding: 1.5em 1.25em 1.45em; }
.topic-card .count { margin: 0 0 .4em; }
/* The card title carries a card this size, so it is set well above the
   framework's 1.24em label. The screen title is still 2.1x it (3.4em against
   1.62em) and is the display face at 400 against this one at 600, which is the
   weight inversion the framework's hierarchy rests on: big-and-light against
   small-and-solid.
   The shadow is not decoration. Cloth is mid-value and high-frequency, and the
   scrim alone cannot promise the same ground under every tradition. */
.topic-card .meta h3, .item-card .meta h3 { text-shadow: 0 2px 12px rgba(8, 6, 4, .8); }
/* Size and colour only, deliberately NO line-height. Every card title in this
   app is line-clamped, so it is set inside overflow: hidden, and a local
   leading here out-specifies the html[lang="hi"] h3 rule at the foot of this
   file. That is not a spilled-ink warning, it is a CLIP: it sheared the top
   matras off बुनाई के गीत and व्यापार-मार्ग, and the ink audit could not see it
   because the audit skips clamped boxes on purpose. Leave the leading to the
   per-script rules and only ever set the size here. */
.topic-card .meta h3 { font-size: 1.62em; color: var(--ink); margin-bottom: 0; }
.topic-card .count, .item-card .badge { text-shadow: 0 1px 8px rgba(8, 6, 4, .85); }

/* Card titles come from the manifest, which is hand-edited on site: never let
   one over-long title blow the card out of the grid. With the shipped titles
   (two or three words) this never fires and nothing on the card is truncated;
   it is here only so a mistyped manifest degrades instead of breaking. */
.card .meta h3 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
                 overflow: hidden; overflow-wrap: anywhere; }

/* align-self: center because the grid row is now full height. Without it the
   "not ready yet" message sat at the top of an 800px cell with the rest of the
   panel empty under it, which reads as a half-loaded screen rather than as a
   deliberate notice. */
.load-error { grid-column: 1 / -1; align-self: center; text-align: center; padding: 4em 0;
              font-family: var(--serif); font-size: 1.4em; color: var(--ink-dim); line-height: 1.7;
              /* the two-line message arrives as one translated string with a
                 newline in it, because data-i18n swaps textContent and a <br>
                 would not survive the swap */
              white-space: pre-line; }

/* ---------- topic screen ----------
   The header block sits on the theme's own cloth (#topic-cloth, painted by
   app.js), so the top of the panel is a hanging rather than 300px of empty
   glass. Everything readable is stacked in one left-aligned column on it:
   count, then title, then summary, which is the order a wall label is set in.
   .topic-hero is flex: 0 0 auto and therefore only as tall as that column,
   which leaves the whole remainder to the strip. */
.topic-hero { position: relative; flex: 0 0 auto; overflow: hidden;
              border-bottom: 1px solid rgba(201, 162, 75, .18);
              box-shadow: 0 16px 38px rgba(0, 0, 0, .5); }
#topic-cloth { position: absolute; inset: 0; z-index: 0;
               width: 100%; height: 100%; object-fit: cover; }
/* The cloth must not compete with the words lying on it. The renderer's full
   scrim is a flat wash and it is not enough on the saturated grounds: the
   summary was running over gold patola diamonds and reading as a fabric shop
   rather than as a gallery label. This lays the room ground back over the
   weave hardest at the left, where all the text is, and lets go towards the
   right, where there is nothing but cloth. Same cloth, one side of it lit. */
.topic-hero::after { content: ''; position: absolute; inset: 0; z-index: 0;
                     pointer-events: none;
                     background: linear-gradient(100deg,
                       rgba(15, 12, 9, .94) 0%, rgba(15, 12, 9, .88) 32%,
                       rgba(15, 12, 9, .5) 68%, rgba(15, 12, 9, .3) 100%); }
#topic .topbar, .topic-desc { position: relative; z-index: 1; }
/* The back control keeps the top row to itself and clears the settings cluster
   with the framework's own measurement. The literal 240px that used to sit
   here was the width of the language pill ALONE: once the type-size control
   joined it in the same cluster the button was painted underneath, a tap on
   "All themes" landed on the type control, and the topic screen had no exit.
   Never a number: kiosk.js remeasures this on every language and type change,
   and the Devanagari faces widen it again when they load. */
#topic .topbar { padding: 1.15em 1.6em 0 var(--kiosk-chrome-clear, 240px); }
/* A restrained typographic control with a hairline edge, not a filled slab.
   The target is unchanged at a full 64px tall and ~200px wide, because the
   padding does that work and padding is invisible: "too basic with big
   buttons" is a complaint about visual bulk, never about hit area. */
#topic-back { border-color: rgba(201, 162, 75, .3); color: var(--ink);
              font-size: 1.08em; padding: .7em 1.4em .7em 1.1em; letter-spacing: .02em; }
/* A chevron at 1.5em in a line-height:1 box put 37px of ink in a 32px line box,
   which is the same ink-outside-its-box fault the Hindi pass at the foot of
   this file exists to stop, and it is worse in Hindi because the button takes
   the Devanagari face and that glyph is taller again (measured 1.33x its own
   size against 1.24x in Latin). Smaller mark, honest line box: the pair keeps
   the button around 68px rather than inflating it to 76px, which is the whole
   point of a restrained control. */
#topic-back .glyph { font-size: 1.25em; color: var(--gold); line-height: 1.4; }
#topic-back:active { background: rgba(201, 162, 75, .2); color: var(--gold); }
/* the count is written in sentence case so one string serves both languages;
   the gallery's small-caps look comes from .eyebrow (a no-op for Devanagari,
   which has no case) */
#topic-count { white-space: nowrap; margin-bottom: .5em; }
/* clamp manifest-driven text so a long title/summary cannot squeeze the media strip
   off the bottom of the screen */
#topic-title { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
               overflow: hidden; overflow-wrap: anywhere;
               /* the room title, set in the same register as the home headline:
                  display face, light, open. Against the 1.34em card titles
                  below it that is a 2.2x step and a weight inversion, which is
                  what tells a standing visitor which line is the room and which
                  lines are the things in it. */
               font-family: var(--display); font-weight: 400; font-size: 2.75em;
               letter-spacing: .008em; margin-bottom: .34em; }
.topic-desc { padding: 1.15em 2.6em 2.1em; }
.topic-desc .body { max-width: 52em; font-size: 1.04em; color: var(--ink-dim);
                    display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
                    overflow: hidden; overflow-wrap: anywhere;
                    text-shadow: 0 1px 10px rgba(8, 6, 4, .9); }
#topic-title, #topic-count { text-shadow: 0 2px 14px rgba(8, 6, 4, .8); }

/* align-items: stretch, not center. Centring is what stranded four short cards
   in the middle of a 660px band with empty glass above and below them; stretch
   makes each story a full-height panel of cloth, which is what the space is
   for. The width is a fraction of the panel so roughly three and a half cards
   are in view: a strip that fits exactly makes the "drag sideways" hint a lie. */
.strip { flex: 1; min-height: 0; display: flex; align-items: stretch; gap: 1.05em;
         overflow-x: auto; overscroll-behavior: contain;
         padding: 1.3em 2.6em; pointer-events: auto; }
.strip::-webkit-scrollbar { height: 0; }
/* grow 1, shrink 0. A theme with four stories overflows the panel at this
   width and scrolls, which is what the "drag sideways" hint promises. A theme
   with three does NOT, and with a fixed basis it left 350px of empty glass on
   the right, which is the stranded-cards fault again in miniature. Growing
   into the space fills the panel for the short themes without letting the long
   ones squeeze themselves down to fit. */
.item-card { flex: 1 0 clamp(340px, 25.5vw, 520px); max-height: 100%; position: relative; }
/* the cloth fills the card and the label sits on it, exactly as on the home
   grid, so the two screens are one visual system rather than two card styles */
.thumbwrap { position: absolute; inset: 0; z-index: 0; }
.item-card .thumb { width: 100%; height: 100%; aspect-ratio: auto; object-fit: cover;
                    background: transparent; }
.item-card .meta { position: relative; z-index: 1; margin-top: auto;
                   padding: 1.35em 1.2em 1.4em; }
/* size only, no leading: see the note on .topic-card .meta h3 above */
.item-card .meta h3 { font-size: 1.34em; margin-bottom: .34em; }
/* Same reasoning as the title clamp above: the shipped captions are one or two
   lines and this never fires. It is here so a caption pasted out of a research
   document degrades instead of pushing the title off the bottom of the card. */
.item-card .meta p { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
                     overflow: hidden; overflow-wrap: anywhere;
                     color: var(--ink-dim); text-shadow: 0 1px 8px rgba(8, 6, 4, .8); }
.badge { position: absolute; top: .9em; left: .9em; z-index: 2;
         display: inline-flex; align-items: center; gap: .45em;
         background: rgba(10, 8, 6, .72); border: 1px solid rgba(201, 162, 75, .55);
         color: var(--gold); border-radius: 999px; padding: .32em 1em;
         font-size: .85em; letter-spacing: .08em; }
/* The one label standing between a visitor and a tap that gives them nothing.
   It used to be --ink on --madder at 3.5:1 and the quietest thing on the card;
   the framework .badge-state carries the fill and the 7.7:1 contrast now, so
   only the placement is left here. Sentence case, not caps: this is the label
   that has to be read at a glance from standing distance. */
/* Top-right, opposite the type badge, NOT bottom-right. The thumb wrapper is
   now the whole card rather than a picture above a caption bar, so a
   bottom-anchored ribbon was landing squarely on the second line of the
   caption. It belongs on the same line as the badge anyway: both answer "what
   is this and can I have it" before a visitor commits a finger. */
.ribbon { position: absolute; right: .9em; top: .9em; z-index: 2;
          text-transform: none; letter-spacing: .02em; }
/* A card with nothing behind it yet still shows its cloth, but the cloth is
   pulled back so the live cards read as the ones to touch. Not grayscale: a
   grey card is exactly the placeholder look this pass exists to remove. */
.item-card.missing .thumb { opacity: .62; filter: saturate(.72); }
/* A card with nothing behind it must not offer the press-in that every live
   card gives. The tap still opens the honest panel, but the card stops
   inviting the tap in the first place. */
.item-card.missing:active { border-color: #3a332a; transform: none; }
/* One quiet line, not a second title. It was competing with the card captions
   at .14em of tracking across the full width of the panel. */
.strip-hint { text-align: center; padding: 0 0 1.15em; letter-spacing: .1em;
              text-transform: uppercase; }

/* ---------- modal overlays (image viewer, audio player) ---------- */
.modal { position: absolute; inset: 0; z-index: 70; display: none;
         background: #0b0906; pointer-events: auto; }
.modal.active { display: block; }
/* Right of the settings cluster, not on top of it. At left:1em the close button
   landed exactly on the language pill (both boxes start at 20px) and, because
   the modal backdrop is z70 against the pill's z56, the toggle was not merely
   covered by the button. The whole pill was buried under the modal. A visitor
   who opened an image had no way back to their language without closing it.
   The 240px that replaced it was the width of the language pill alone. The
   cluster now carries the type-size control too and measures ~427px, so the
   literal put the ✕ back underneath the chrome: a tap on close landed on the
   type control and the viewer could not be shut. The framework publishes the
   real measurement and keeps it current, so ask it rather than counting. */
.modal-close { position: absolute; top: 1em; left: var(--kiosk-chrome-clear, 240px); z-index: 75; }

/* The local z-index that used to sit here is gone. kiosk.css now puts the
   toggle at z110, above the screensaver as well as this app's modals, once,
   for all seven stations. The local 76 was in fact BELOW the screensaver's
   z100, so the toggle was invisible on the very first screen a visitor met.
   The film-player exception is also framework-owned now; nothing app-local
   is needed on either count. */

/* image / coming viewer */
#viewer-img { position: absolute; top: 120px; left: 130px;
              width: calc(100% - 260px); height: calc(100% - 260px);
              object-fit: contain; }
#viewer-img.hide { display: none; }
/* A hanging and a label beside it, which is how the piece this stands in for
   would actually be shown. The old panel was a small dimmed 16:9 box floating
   in the middle of 1080px of black with the type stranded beside it. */
#viewer-coming { position: absolute; inset: 0; display: none;
                 align-items: center; justify-content: center; gap: 4em; padding: 0 6%; }
#viewer-coming.show { display: flex; }
#coming-art { flex: 0 0 auto; width: 42%; max-width: 760px; height: 68vh;
              aspect-ratio: auto; object-fit: cover;
              border-radius: var(--radius); border: 0;
              box-shadow: 0 20px 52px rgba(0, 0, 0, .55); }
.coming-text { max-width: 26em; }
.coming-text .caption { letter-spacing: .25em; color: var(--gold);
                        margin-bottom: .6em; text-transform: uppercase; }
/* 1.32 rather than the framework's 1.2: at 2.05em EB Garamond wants 46px of
   ink in a 44px line box, so the descender of "Coming soon" was being clipped
   by this heading's own line clamp. Specificity is checked: html[lang="hi"] h2
   in section 2 still out-specifies this, so Devanagari keeps its own leading. */
.coming-text h2 { margin-bottom: .5em; line-height: 1.32; }

/* left padding = the settings cluster + the close button + gaps. The literal
   324px it replaces was 240 + 64 + 20, and the 240 in it was the language pill
   before the type-size control joined it in the same cluster. */
.viewer-top { position: absolute; top: 0; left: 0; right: 0; z-index: 72;
              display: flex; align-items: center; gap: 1.2em;
              padding: 1.1em 1.5em 2.2em calc(var(--kiosk-chrome-clear, 240px) + 4.4em);
              pointer-events: none;
              background: linear-gradient(rgba(6, 5, 3, .85), transparent); }
.viewer-titles { flex: 1; display: flex; align-items: baseline; gap: 1.2em; min-width: 0; }
.viewer-titles h3 { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* 84px of target, but a quieter ring than the framework's solid gold edge: on
   an otherwise black screen these two were the loudest objects on it. */
.viewer-nav { position: absolute; top: 50%; transform: translateY(-50%); z-index: 72;
              width: 84px; height: 84px; font-size: 1.9em;
              border-color: rgba(201, 162, 75, .42); background: rgba(10, 8, 6, .5); }
.viewer-nav.prev { left: 1.1em; }
.viewer-nav.next { right: 1.1em; }
.viewer-capbar { position: absolute; left: 0; right: 0; bottom: 0; z-index: 71;
                 padding: 2.2em 9em 1.5em; text-align: center; pointer-events: none;
                 background: linear-gradient(transparent, rgba(6, 5, 3, .85)); }
.viewer-capbar p { color: var(--ink-dim); font-weight: 300; font-size: 1.02em;
                   display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
                   overflow: hidden; overflow-wrap: anywhere; }
.coming-text h2, .coming-text .caption { overflow-wrap: anywhere; }
.coming-text h2 { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
                  overflow: hidden; }

/* audio player */
#audiov.active { display: flex; align-items: center; justify-content: center; }
.au-inner { display: flex; align-items: center; gap: 4.5em; padding: 0 5em; max-width: 1640px; }
.au-artwrap { position: relative; flex: 0 0 620px; }
/* A square woven panel rather than a 16:9 film still. Nothing about a
   listening post is widescreen, and the square is the shape a visitor already
   reads as "this is the artwork for the thing that is playing". */
#au-art { display: block; width: 620px; aspect-ratio: 1 / 1; object-fit: cover;
          border-radius: var(--radius); border: 0;
          box-shadow: 0 20px 52px rgba(0, 0, 0, .55); }
/* The bars are the only thing on this screen that says sound is coming out,
   and gold-on-cloth they were invisible. They get their own dark plate now,
   which is also what stops them landing on a pale weave and disappearing. */
.au-eq { position: absolute; right: 1em; top: 1em; display: flex; gap: 6px;
         align-items: flex-end; height: 34px;
         background: rgba(10, 8, 6, .72); border-radius: 999px;
         padding: .55em .9em; box-sizing: content-box; }
.au-eq i { width: 7px; background: var(--gold); border-radius: 3px; height: 30%;
           animation: eq .9s ease-in-out infinite alternate; animation-play-state: paused; }
.au-eq i:nth-child(2) { animation-delay: .18s; }
.au-eq i:nth-child(3) { animation-delay: .36s; }
.au-eq i:nth-child(4) { animation-delay: .1s; }
.au-eq i:nth-child(5) { animation-delay: .28s; }
#audiov.playing .au-eq i { animation-play-state: running; }
@keyframes eq { from { height: 22%; } to { height: 100%; } }

.au-panel { flex: 1; min-width: 0; }
.au-kicker { letter-spacing: .28em; color: var(--gold); text-transform: uppercase; margin-bottom: .8em; }
#au-count { letter-spacing: .18em; }
#au-title { margin-bottom: .45em;
            display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
            overflow: hidden; overflow-wrap: anywhere; }
#au-cap { max-width: 34em;
          display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical;
          overflow: hidden; overflow-wrap: anywhere; }
.au-seekrow { display: flex; align-items: center; gap: 1.1em; margin-top: 2em; }
.au-seekrow .time { font-family: var(--sans); font-size: .9em; color: var(--ink-dim);
                    min-width: 4.5em; text-align: center; }
.au-controls { display: flex; align-items: center; gap: 1.1em; margin-top: 1.2em; }
.au-controls .pbtn.big { width: 88px; height: 88px; font-size: 1.8em; }

/* ---------- volume control (shared: video bar + audio panel) ---------- */
.volwrap { position: relative; }
.volwrap .vol-btn { font-size: .92em; font-family: var(--sans); letter-spacing: .04em; }
.vol-fly { position: absolute; bottom: 80px; right: 0; z-index: 75;
           display: none; align-items: center; gap: 1em;
           background: var(--bg-raised); border: 1px solid #3a332a;
           border-radius: var(--radius); padding: .7em 1.3em; }
.vol-fly.open { display: flex; }
.vol-fly .caption { letter-spacing: .18em; text-transform: uppercase; }
/* 64px tall: the rail is thin but the finger target is not (was 56px) */
.vol-track { position: relative; width: 260px; height: 64px; pointer-events: auto; }
.vol-rail { position: absolute; left: 0; right: 0; top: 28px; height: 8px;
            background: #3a332a; border-radius: 4px; }
.vol-fill { position: absolute; left: 0; top: 28px; height: 8px;
            background: var(--gold); border-radius: 4px; width: 80%; }

/* The local seek-bar enlargement that used to live here has been removed:
   kiosk.css now builds .seek as a proper 64px target (content-box, 8px of
   visible rail), which #audiov .seek inherits too. Re-adding a height here
   would stack on top of that padding and give a 120px bar. */

/* ---------- extras injected into the framework video player ---------- */
.vx-top { position: absolute; top: 0; left: 0; right: 0; z-index: 61;
          display: flex; align-items: flex-start; justify-content: space-between; gap: 2em;
          padding: 1.15em 1.5em 2.6em 6.4em; pointer-events: none;
          background: linear-gradient(rgba(0, 0, 0, .82), transparent);
          transition: opacity .4s; }
.vx-top.faded { opacity: 0; }
.vx-text { min-width: 0; }
.vx-title { font-family: var(--serif); font-size: 1.45em; color: var(--ink);
            display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
            overflow: hidden; overflow-wrap: anywhere; }
.vx-cap { margin-top: .25em; display: -webkit-box; -webkit-line-clamp: 2;
          -webkit-box-orient: vertical; overflow: hidden; overflow-wrap: anywhere; }
.vx-right { text-align: right; flex: 0 0 auto; }
.vx-count { letter-spacing: .18em; color: var(--gold); white-space: nowrap; }
.vx-time { font-family: var(--sans); color: var(--ink-dim); white-space: nowrap; padding-top: .35em; }


/* ── 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; }

/* ==========================================================================
   GALLERY LEGIBILITY AUDIT (app-local corrections)
   Panel: 1920x1080 landscape, visitors standing 40-80 cm away.
   Framework .caption is .82em (16.4px here) in --ink-faint (3.84:1 on --bg,
   3.15:1 on --bg-card), under both the ~18px gallery floor and AA body 4.5:1.
   Every selector below is app-owned; the framework-wide fix is reported.
   ========================================================================== */
.strip-hint,
.au-kicker,
#au-count,
#viewer-count,
#coming-label   { font-size: .95em; }
/* The topic count is now the eyebrow above the room title rather than a
   caption tucked beside it, so it carries the .eyebrow look but keeps a floor
   of its own: .eyebrow's max(18px,.72em) would be a regression against the
   .95em (19px) this line was audited at. */
#topic-count    { font-size: max(20px, .95em); }
/* D7: the card meta / secondary line sits on a 22px floor, so the count under a
   topic card and the caption under a strip card can be read standing at the
   15.6-inch panel, not only at the 32-inch one. These are the two card-meta
   lines this station owns; the framework carries the same floor for cards that
   do not override it, and --meta-size is its one number for it. The literal is
   the fallback for the interval before that token lands. Only the meta line
   moves (D7 is conservative): nothing else in the type scale changes. */
/* Keeps its 22px floor even though it now wears .eyebrow, whose own floor is
   18px: this is still the card meta line D7 raised, it is just set above the
   title instead of below it. */
.topic-card .count { font-size: var(--meta-size, max(22px, .9em)); color: var(--gold); }
.card .meta p      { font-size: var(--meta-size, max(22px, .9em)); }
.strip-hint, #viewer-count { color: var(--ink-dim); }
.badge  { font-size: .95em; }
.viewer-titles .caption { color: var(--ink-dim); }
/* the framework .badge-state sets .9em; the Coming soon label is the one that
   has to be read from standing distance, so it goes up rather than down */
.ribbon { font-size: .95em; }


/* ==========================================================================
   HINDI LAYOUT PASS (measured on the real panel at 1920x1080)
   Devanagari sets taller than Latin (ascender matras + the descending vowel
   signs) and most of these strings run 10-25% longer. Everything below was
   added because a Hindi screen actually broke, not on principle.
   ========================================================================== */

/* 1. Letter-spacing. The gallery's small-caps captions are tracked out
   (.12em-.35em). Devanagari is a connected script: tracking pushes each
   glyph off its shirorekha and the matras drift away from the letter they
   belong to. It also cost enough width to wrap the screensaver eyebrow onto
   two lines. Tracking goes back to normal for Hindi. */
html[lang="hi"] #topic-count,
html[lang="hi"] .strip-hint,
html[lang="hi"] .badge,
html[lang="hi"] .ribbon,
html[lang="hi"] .au-kicker,
html[lang="hi"] #au-count,
html[lang="hi"] .vx-count,
html[lang="hi"] .coming-text .caption,
html[lang="hi"] .eyebrow,
html[lang="hi"] .vol-fly .caption { letter-spacing: normal; }

/* 2. Line boxes. Devanagari's ink runs from the top matra to the descending
   vowel sign, so its natural line box is about 1.47em, taller than the
   framework's 1.15/1.25 display leading. Measured on the home headline: the
   h1 wanted 94px of ink inside a 90px line box, and the glyph spans inside
   .badge wanted 31px inside 28px. Nothing is clipped (overflow is visible)
   but the ink was sitting outside its own box and into the element above. */
html[lang="hi"] h1, html[lang="hi"] .h1,
html[lang="hi"] #saver h1 { line-height: 1.55; }   /* out-specifies #saver h1 above */
html[lang="hi"] h2, html[lang="hi"] .h2 { line-height: 1.55; }
html[lang="hi"] h3, html[lang="hi"] .h3 { line-height: 1.55; }
html[lang="hi"] .badge, html[lang="hi"] .ribbon,
html[lang="hi"] .caption { line-height: 1.6; }

/* 3. Uppercasing is a no-op for Devanagari, so these rules only ever applied
   to English; spelling that out keeps a later editor from "fixing" the
   sentence-case Hindi strings. */
html[lang="hi"] .strip-hint,
html[lang="hi"] #topic-count,
html[lang="hi"] .au-kicker,
html[lang="hi"] .eyebrow,
html[lang="hi"] .coming-text .caption { text-transform: none; }

/* 4. The room title borrows the display face, which carries no Devanagari at
   all, so Hindi would fall through to whatever generic serif the box happens
   to have: on a sealed offline kiosk that is a coin toss between a wrong face
   and a row of empty boxes. Same reason as every other rule here that names
   the bundled face by hand. Face only: the leading is left to the h2 rule in
   section 2, because #topic-title is line-clamped and a local leading here
   would clip Devanagari rather than merely crowd it. */
html[lang="hi"] #topic-title { font-family: 'Noto Serif Devanagari', var(--serif); }

/* 5. The home headline and the back chevron need their own Hindi leading
   BECAUSE this file now gives each of them an English one. `#home .hero h1`
   out-specifies the `html[lang="hi"] h1` rule in section 2 above, so the moment
   a local leading was set for EB Garamond's ink, Devanagari silently lost its
   compensation: करघे की जीवंत परंपराएँ put 92px of ink in a 79px box at medium
   and 114px in 98px at large. The value is 1.55 because that is what section 2
   already uses for every other h1 on this station, arrived at the same way.
   This is the trap the framework flagged: any leading change has to be checked
   in Hindi at the LARGE step, which is where a visitor who needs the control
   will be, and where a local English fix does the most damage. */
html[lang="hi"] #home .hero h1 { line-height: 1.55; }
html[lang="hi"] #topic-back .glyph { line-height: 1.62; }

/* ==== 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; }
