/* ==========================================================================
   The mobile app surface. Loaded after tokens.css, instead of site.css.

   site.css dresses the public pages, which are read on anything from a phone
   to a projector. This file dresses one thing only: a phone held by a KWT
   member or shown to a visitor. So it assumes a single column, a fixed tab
   bar in the thumb zone, and type that survives a 60 year old eye in daylight.

   Three rules inherited from floc and kept intact:
     --green stays reserved for WhatsApp. Positive numbers and "good" status
     therefore use --ok, a separate semantic token declared below, so the two
     never collide and the WhatsApp affordance keeps its meaning.
     --bg-soft is only used where there is no photograph.
     Anything painted on an accent takes --on-accent, never a hardcoded #fff.

   Spacing is an 8 point grid expressed in rem: .5 / 1 / 1.5 / 2 / 3 / 4.
   ========================================================================== */

:root{
  /* Page ground. Cooler and lighter than --bg-soft, which is the cream used
     for chat bubbles; the two need to read as different surfaces when they
     sit on screen together. */
  --app-bg:#F4F6F6;
  --app-line:#E4EAEA;

  /* Semantic status. Declared for both themes in this file rather than inside
     a media query, which is the rule tokens.css sets and the reason the public
     site never renders one theme's text on the other theme's ground. */
  --ok:#17795A;
  --ok-tint:#E4F2EC;
  --warn:#96590A;
  --warn-tint:#FBF0DC;
  --info-tint:#E2F0F1;

  --tab-h:4rem;
  --tab-lift:.75rem;
  --appbar-h:3.75rem;
}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]){
    --app-bg:#0B1214;
    --app-line:#223235;
    --ok:#5CC39B;
    --ok-tint:#14312A;
    --warn:#E7A542;
    --warn-tint:#33280F;
    --info-tint:#12292B;
  }
}
:root[data-theme="dark"]{
  --app-bg:#0B1214; --app-line:#223235;
  --ok:#5CC39B; --ok-tint:#14312A;
  --warn:#E7A542; --warn-tint:#33280F;
  --info-tint:#12292B;
}

/* ---------------------------------------------------------------- shell */

*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0;
  background:var(--app-bg);
  color:var(--ink);
  /* 19px floor. PLAN_OF_ACTION section 2 treats this as a constraint, not a
     preference: the users are women in their 50s to 70s reading outdoors. */
  font:400 1.1875rem/1.55 "IBM Plex Sans",system-ui,sans-serif;
  -webkit-font-smoothing:antialiased;
  overscroll-behavior-y:none;
}
h1,h2,h3,h4{margin:0;font-family:"Montserrat",system-ui,sans-serif;letter-spacing:-.02em;line-height:1.15}
p{margin:0}
ul{margin:0;padding:0;list-style:none}
img{display:block;max-width:100%}
a{color:inherit;text-decoration:none}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}

.skip{position:absolute;left:-9999px;top:0;z-index:100;padding:1rem;background:var(--surface);color:var(--blue)}
.skip:focus{left:0}

:where(a,button,[tabindex]):focus-visible{
  outline:3px solid var(--blue);
  outline-offset:2px;
  border-radius:.25rem;
}
/* The router focuses each screen's heading so a screen reader announces the
   new screen. That heading is not a control, so it takes no visible ring. */
.screen h1:focus,.screen h1:focus-visible{outline:none}

.ic{width:1.5rem;height:1.5rem;flex:none;fill:none;stroke:currentColor;
  stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}

/* The phone column. On a laptop the app sits centred in a device-width
   column so a demo on a projector still reads as an app rather than a
   stretched page. */
.app{
  max-width:26.75rem;
  margin:0 auto;
  min-height:100vh;
  min-height:100dvh;
  background:var(--app-bg);
  position:relative;
  box-shadow:0 0 0 1px var(--app-line);
}

/* One section per screen. The router unhides exactly one. Using hidden
   rather than display keeps the markup honest for assistive tech. */
.screen[hidden]{display:none}
.screen{padding-bottom:calc(var(--tab-h) + var(--tab-lift) + env(safe-area-inset-bottom) + 1rem)}

/* ---------------------------------------------------------------- app bar */

.appbar{
  position:sticky;top:0;z-index:20;
  display:flex;align-items:center;gap:.625rem;
  min-height:var(--appbar-h);
  padding:.75rem 1rem;
  padding-top:calc(.75rem + env(safe-area-inset-top));
}

.appbar .who{flex:1;min-width:0}
.appbar .who b{display:block;font:700 1rem/1.2 "Montserrat",sans-serif;letter-spacing:-.02em;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.appbar .who span{display:block;font-size:.8125rem;color:var(--ink-muted);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.appbar h1{flex:1;min-width:0;font-size:1.25rem}

/* Round 48px controls, 64px tap area via the ::before spread. Visible size
   and touch size are decoupled so the bar stays light without shrinking the
   target below what a shaky hand can hit. */
.iconbtn{
  position:relative;width:3rem;height:3rem;flex:none;
  display:grid;place-items:center;border-radius:50%;
  border:1px solid var(--app-line);background:var(--surface);color:var(--ink);
  transition:background .18s var(--ease-state);
}
.iconbtn::before{content:"";position:absolute;inset:-.5rem}
.iconbtn:hover{background:var(--info-tint)}
.iconbtn .ic{width:1.375rem;height:1.375rem}

/* Unread dot. Carries a visually hidden word too, because colour is never
   the only signal in this app. */
.iconbtn .dot{position:absolute;top:.4375rem;right:.4375rem;
  width:.5625rem;height:.5625rem;border-radius:50%;
  background:var(--orange);box-shadow:0 0 0 2px var(--surface)}

.vh{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap;border:0}

/* ---------------------------------------------------------------- blocks */

.pad{padding:1.5rem 1rem 0}
.eyebrow{
  font:600 .75rem/1 "IBM Plex Sans",sans-serif;
  letter-spacing:.14em;text-transform:uppercase;color:var(--orange-text);
}
.h-page{margin-top:.75rem;font-size:1.75rem}
.h-page .hl{color:var(--blue)}
.lede{margin-top:.5rem;font-size:1rem;color:var(--ink-muted);line-height:1.5}

.h-sec{margin-top:2rem;font-size:1.3125rem}
.h-sec+.lede{margin-top:.375rem}

/* wrap, and min-width:0 on the heading. A flex child defaults to min-width
   auto and refuses to shrink below its own text, which at 360px pushes the
   pill off the right edge and takes the whole page into horizontal scroll. */
.sec-row{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;
  gap:.5rem 1rem;margin-top:2rem}
.sec-row h2{min-width:0;font-size:1.25rem}
.sec-row .count{font-size:.875rem;color:var(--ink-muted);white-space:nowrap}
.sec-row .pill{font-size:.75rem;padding:.25rem .5625rem}

.note{
  margin-top:1rem;font-size:.875rem;line-height:1.5;color:var(--ink-muted);
}
/* Padded to a 44px target. It is only a credit line, but it is still a link,
   and a 22px tall link is not one anybody can reliably hit. */
.byfloc{display:block;margin-top:1.5rem;padding:.75rem 1rem;text-align:center;
  font-size:.875rem;color:var(--ink-muted)}
.byfloc b{color:var(--blue);font-weight:600}

/* ---------------------------------------------------------------- pills */

.pill{
  display:inline-flex;align-items:center;gap:.375rem;
  padding:.3125rem .625rem;border-radius:var(--r-pill);
  font:600 .8125rem/1.2 "IBM Plex Sans",sans-serif;white-space:nowrap;
}
.pill--ok{background:var(--ok-tint);color:var(--ok)}
.pill--warn{background:var(--warn-tint);color:var(--warn)}
.pill--info{background:var(--info-tint);color:var(--blue-deep)}
.pill::before{content:"";width:.4375rem;height:.4375rem;border-radius:50%;background:currentColor}
.pill--plain::before{display:none}

/* ---------------------------------------------------------------- cards */

.card{
  background:var(--surface);
  border:1px solid var(--app-line);
  border-radius:1.25rem;
  box-shadow:var(--sh1);
}

/* Tinted square behind an icon. Three tints, one per action family, so the
   Catat menu is scannable by colour AND by icon AND by its label. */
.tile{
  width:3.25rem;height:3.25rem;flex:none;
  display:grid;place-items:center;border-radius:.875rem;
}
.tile .ic{width:1.625rem;height:1.625rem}
.tile--panen{background:var(--info-tint);color:var(--blue-deep)}
.tile--jual{background:var(--ok-tint);color:var(--ok)}
.tile--rawat{background:var(--warn-tint);color:var(--warn)}

/* Row that opens something. 64px minimum, chevron on the right. */
.row{
  display:flex;align-items:center;gap:1rem;width:100%;text-align:left;
  min-height:4rem;padding:1rem;
  background:var(--surface);border:1px solid var(--app-line);
  border-radius:1.25rem;box-shadow:var(--sh1);
  transition:background .18s var(--ease-state),transform .18s var(--ease-state);
}
.row+.row{margin-top:.75rem}
.row:hover{background:color-mix(in srgb,var(--blue) 4%,var(--surface))}
.row:active{transform:scale(.99)}
.row .txt{flex:1;min-width:0}
.row .txt b{display:block;font:700 1.0625rem/1.3 "IBM Plex Sans",sans-serif}
.row .txt p{margin-top:.25rem;font-size:.9375rem;line-height:1.45;color:var(--ink-muted)}
.row .chev{width:1.25rem;height:1.25rem;flex:none;color:var(--ink-muted)}

/* ---------------------------------------------------------------- tab bar */

/* A floating pill in the thumb zone, lifted off the bottom edge and clear of
   the home indicator. Its material lives in glass.css. The capsule behind the
   current tab is one element, slid by --i, which js/app.js sets. */
.tabbar{
  position:fixed;z-index:30;left:50%;
  bottom:calc(var(--tab-lift) + env(safe-area-inset-bottom));
  width:calc(min(100vw, 26.75rem) - 2rem);
  transform:translateX(-50%);
  display:grid;grid-template-columns:repeat(var(--n,4),1fr);
  height:var(--tab-h);padding:.3125rem;
  border-radius:var(--r-pill);
}
/* Reviewer language switch, js/i18n.js. It sits in each screen's bar, before
   the bell; hidden until the script runs, since it does nothing without it. */
.langbtn{
  flex:none;position:relative;
  min-width:2.75rem;height:2.25rem;padding:0 .625rem;border-radius:var(--r-pill);
  font:700 .8125rem/1 "Montserrat",sans-serif;letter-spacing:.04em;color:var(--ink);
  border:1px solid var(--app-line);background:var(--surface);
}
.langbtn[aria-pressed="true"]{background:var(--blue);border-color:var(--blue);color:#fff}
.langbtn[hidden]{display:none}
.langbtn::before{content:"";position:absolute;inset:-.375rem}
.langbtn--float{position:fixed;z-index:31;top:calc(.75rem + env(safe-area-inset-top));right:1rem}
.tab-cap{
  position:absolute;top:.3125rem;bottom:.3125rem;left:.3125rem;
  width:calc((100% - .625rem) / var(--n,4));
  border-radius:var(--r-pill);
  transform:translateX(calc(var(--i,0) * 100%));
  transition:transform 200ms cubic-bezier(.2,.7,.2,1);
}
.tabbar.tanpa-tab .tab-cap{opacity:0}
.tab{
  position:relative;z-index:1;
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.1875rem;
  min-height:2.75rem;border-radius:var(--r-pill);
  font:500 .75rem/1 "IBM Plex Sans",sans-serif;
  color:var(--ink);
  -webkit-tap-highlight-color:transparent;
}
.tab .ic{width:1.5rem;height:1.5rem;transition:transform 160ms cubic-bezier(.2,.7,.2,1)}
.tab:active .ic{transform:scale(.9)}
.tab[aria-current="page"]{color:var(--blue-deep);font-weight:700}
.tab[aria-current="page"] .ic{stroke-width:2}

/* ---------------------------------------------------------------- kebun */

/* The cycle card. The one place in the app that carries a gradient, because
   it is the single most important fact on the screen: what is growing and
   how long until it can be picked. */
.cycle{
  margin-top:1.5rem;padding:1.5rem;border-radius:1.5rem;color:#FFFFFF;
  background:linear-gradient(155deg,#17636A 0%,#0E4A50 55%,#0B3A3F 100%);
  box-shadow:0 1rem 2rem rgba(11,58,63,.28);
}
.cycle .tag{
  display:inline-flex;align-items:center;gap:.4375rem;white-space:nowrap;
  padding:.3125rem .75rem;border-radius:var(--r-pill);
  background:rgba(255,255,255,.14);border:1px solid rgba(255,255,255,.22);
  font:600 .75rem/1 "IBM Plex Sans",sans-serif;letter-spacing:.1em;text-transform:uppercase;
}
.cycle .tag::before{content:"";width:.4375rem;height:.4375rem;border-radius:50%;background:#7FE0B4}
.cycle h2{margin-top:1rem;font-size:1.875rem;color:#FFFFFF}
.cycle h2 .hl{color:#7FE0B4}
.cycle .sub{margin-top:.5rem;font-size:.9375rem;color:rgba(255,255,255,.78)}

.cycle .bar-head{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;
  gap:.5rem 1rem;margin-top:1.5rem}
.cycle .bar-head span{min-width:0;font:600 .875rem/1.2 "IBM Plex Sans",sans-serif}
.cycle .bar-head .tag{text-transform:none;letter-spacing:0;font-size:.75rem;padding:.3125rem .625rem}
.cycle .bar-head .tag::before{display:none}

.cycle .bar{margin-top:.75rem;height:.5rem;border-radius:var(--r-pill);
  background:rgba(255,255,255,.18);overflow:hidden}
.cycle .bar i{display:block;height:100%;border-radius:var(--r-pill);
  background:linear-gradient(90deg,#7FE0B4,#BFF0D8)}
.cycle .target{margin-top:.625rem;text-align:right;font-size:.8125rem;color:rgba(255,255,255,.72)}

/* Sensors, 2x2. Value is the loudest thing in the card: the label "Nutrisi"
   must never out-shout the number 510. */
.sensors{display:grid;grid-template-columns:1fr 1fr;gap:.75rem;margin-top:1rem}
.sensor{padding:1rem;background:var(--surface);border:1px solid var(--app-line);
  border-radius:1.25rem;box-shadow:var(--sh1)}
.sensor .top{display:flex;align-items:flex-start;justify-content:space-between;gap:.5rem}
.sensor .top .ic{width:1.375rem;height:1.375rem}
.sensor .k{display:block;margin-top:.875rem;font-size:.8125rem;color:var(--ink-muted)}
.sensor .v{
  display:flex;align-items:baseline;gap:.25rem;margin-top:.125rem;
  font:800 1.75rem/1.1 "Montserrat",sans-serif;font-variant-numeric:tabular-nums;
}
.sensor .v u{font:500 .8125rem/1 "IBM Plex Sans",sans-serif;text-decoration:none;color:var(--ink-muted)}
.sensor .ideal{display:block;margin-top:.375rem;font-size:.75rem;color:var(--ink-muted)}
.sensor[data-state="warn"] .v{color:var(--warn)}
.sensor[data-state="warn"] .top .ic{color:var(--warn)}
.sensor[data-state="ok"] .top .ic{color:var(--ok)}

/* The advice card. Always a quantity and a place, per the KWT UI rules.
   No coloured left stripe: the tint, the icon and the bold amber heading
   already carry the emphasis, and a ribbon down one edge adds nothing. */
.advice{
  display:flex;gap:1rem;margin-top:1rem;padding:1.25rem;
  border-radius:1.25rem;background:var(--warn-tint);
  border:1px solid color-mix(in srgb,var(--warn) 30%,transparent);
}
.advice .ic{width:1.5rem;height:1.5rem;color:var(--warn)}
.advice b{display:block;font:700 1.0625rem/1.3 "IBM Plex Sans",sans-serif;color:var(--warn)}
.advice p{margin-top:.375rem;font-size:1rem;line-height:1.5}

/* ---------------------------------------------------------------- riwayat */

.log-row{display:flex;align-items:center;gap:1rem;padding:1rem 0;border-bottom:1px solid var(--app-line)}
.log-row:last-child{border-bottom:0}
.log-row .txt{flex:1;min-width:0}
.log-row .txt b{display:block;font:700 1.0625rem/1.3 "IBM Plex Sans",sans-serif}
.log-row .txt span{display:block;margin-top:.125rem;font-size:.875rem;color:var(--ink-muted)}
.log-row .amt{font:700 1.0625rem/1.2 "IBM Plex Sans",sans-serif;font-variant-numeric:tabular-nums;white-space:nowrap}
.log-row .amt[data-kind="plus"]{color:var(--ok)}

/* ---------------------------------------------------------------- proyek */

/* One project, one card, one photograph. Built for a funder scrolling with a
   thumb, so the image leads and the sentence underneath is plain Indonesian
   with no numbers that site.json cannot back up. */
.proyek{display:block;margin-top:1rem;overflow:hidden;border-radius:1.5rem;
  background:var(--surface);border:1px solid var(--app-line);box-shadow:var(--sh2);
  transition:transform .2s var(--ease-state),box-shadow .2s var(--ease-state)}
.proyek:active{transform:scale(.99)}
.proyek .shot{position:relative;aspect-ratio:16/10;background:var(--info-tint)}
.proyek .shot img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
/* No photograph: the line-art icon stands in, centred on its tint. It is a
   treatment, not an apology, and it never pretends to be a photo. */
.proyek .shot.kosong{display:grid;place-items:center}
.proyek .shot.kosong img{display:none}
.proyek .shot.kosong::after{content:""}
.proyek .shot .art{width:4.5rem;height:4.5rem;color:var(--blue);opacity:.55;stroke-width:1.2}
.proyek .shot:not(.kosong) .art{display:none}
/* Posters and scanned documents fit whole. Cropping a certificate to a 16:10
   card is how a funder ends up looking at half a table. */
.shot--utuh img{object-fit:contain !important;background:var(--info-tint)}
/* Line art does not need a photo-sized box to sit in. .screen prefix so this
   also beats `.detail .shot`, which sets a taller ratio further down. */
.screen .shot.kosong{aspect-ratio:2/1}
.proyek .body{padding:1.25rem}
.proyek .body h3{font-size:1.1875rem}
.proyek .body p{margin-top:.5rem;font-size:1rem;line-height:1.5;color:var(--ink-muted)}
.proyek .more{display:inline-flex;align-items:center;gap:.375rem;margin-top:1rem;
  font:600 .9375rem/1 "IBM Plex Sans",sans-serif;color:var(--blue)}
.proyek .more .ic{width:1.125rem;height:1.125rem}

/* Award band. Leads the funder screen because it is the strongest verifiable
   claim the RW has, and it is cited the way the RW itself cites it. */
.award{
  margin-top:1.5rem;padding:1.5rem;border-radius:1.5rem;
  background:linear-gradient(150deg,#1B6E76 0%,#12545B 100%);
  color:#FFFFFF;box-shadow:0 1rem 2rem rgba(11,58,63,.24);
}
.award .ic{width:2rem;height:2rem;color:#F3C677}
.award b{display:block;margin-top:.875rem;font:700 1.1875rem/1.35 "Montserrat",sans-serif;
  letter-spacing:-.01em;color:#FFFFFF}
.award p{margin-top:.625rem;font-size:.9375rem;line-height:1.5;color:rgba(255,255,255,.82)}
.award .surat{margin-top:.875rem;padding-top:.875rem;border-top:1px solid rgba(255,255,255,.18);
  font-size:.8125rem;color:rgba(255,255,255,.7)}
a.award{display:block;transition:transform .2s var(--ease-state)}
a.award:active{transform:scale(.99)}
.award .more{display:inline-flex;align-items:center;gap:.375rem;margin-top:1rem;
  font:600 .9375rem/1 "IBM Plex Sans",sans-serif;color:#FFFFFF}
.award .more .ic{width:1.125rem;height:1.125rem;color:#FFFFFF}

/* ---------------------------------------------------------------- beranda */

/* The gate photo stands on its own. The sign in it already says the
   kampung's name, so nothing is laid over it; the crop keeps the sign whole. */
.gerbang{margin-top:1rem;border-radius:1.5rem;overflow:hidden;box-shadow:var(--sh2)}
.gerbang .foto{aspect-ratio:4/3}
.gerbang img{object-position:50% 78%}

.sec-sub{margin-top:.375rem;font-size:.9375rem;line-height:1.5;color:var(--ink-muted)}

/* A facility card, in the RW's own format: photo, tile, name, sentence. */
.fas{display:block;margin-top:1rem;overflow:hidden;border-radius:1.5rem;
  transition:transform .2s var(--ease-state)}
a.fas:active{transform:scale(.99)}
.fas .shot{position:relative;aspect-ratio:16/10;background:var(--info-tint)}
.fas .shot img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.fas .shot.kosong{display:grid;place-items:center}
.fas .shot .art{width:4.5rem;height:4.5rem;color:var(--blue);opacity:.55;stroke-width:1.2}
.fas .shot:not(.kosong) .art{display:none}
.fas-body{display:flex;gap:1rem;align-items:flex-start;padding:1.25rem}
.tile--fas{background:var(--info-tint);color:var(--blue)}
.fas-txt{min-width:0}
.fas-txt h3{font-size:1.1875rem;line-height:1.25}
.fas-txt p{margin-top:.375rem;font-size:1rem;line-height:1.5;color:var(--ink-muted)}
.fas .more{display:inline-flex;align-items:center;gap:.375rem;margin-top:.875rem;
  font:600 .9375rem/1 "IBM Plex Sans",sans-serif;color:var(--blue)}
.fas .more .ic{width:1.125rem;height:1.125rem}

/* One article as a row. 64px minimum, the picture as a square thumbnail. */
.kabar-list{display:grid;gap:.625rem;margin-top:1rem}
.kabar{display:flex;align-items:center;gap:.875rem;padding:.625rem;border-radius:1.25rem;
  transition:transform .18s var(--ease-state)}
.kabar:active{transform:scale(.99)}
.kabar .foto{width:4.5rem;height:4.5rem;flex:none;border-radius:.875rem}
.kabar .foto.kosong{display:grid;place-items:center;color:var(--blue)}
.kabar .txt{flex:1;min-width:0}
.kabar time{display:block;font-size:.8125rem;color:var(--ink-muted)}
.kabar b{display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;
  margin-top:.25rem;font:700 1rem/1.3 "IBM Plex Sans",sans-serif}
.kabar .chev{width:1.125rem;height:1.125rem;flex:none;color:var(--ink-muted)}

.profil p{margin-top:.75rem;font-size:1rem;line-height:1.6}
.pengurus{margin-top:1.25rem}
.ketua{display:flex;align-items:center;gap:1rem;padding:1rem 1.25rem;border-radius:1.25rem}
.seal{width:3.25rem;height:3.25rem;flex:none;display:grid;place-items:center;border-radius:.875rem;
  background:var(--blue);color:var(--on-accent);text-align:center;
  font:800 .75rem/1.05 "Montserrat",sans-serif}
.ketua div span{display:block;font:600 .75rem/1 "IBM Plex Sans",sans-serif;letter-spacing:.12em;
  text-transform:uppercase;color:var(--ink-muted)}
.ketua b{display:block;margin-top:.375rem;font:700 1.1875rem/1.2 "Montserrat",sans-serif}
.rts{display:grid;grid-template-columns:1fr 1fr;gap:.625rem;margin-top:.625rem}
.rt{padding:.875rem 1rem;border-radius:1rem}
.rt span{display:block;font:600 .75rem/1 "IBM Plex Sans",sans-serif;letter-spacing:.1em;color:var(--orange-text)}
.rt b{display:block;margin-top:.375rem;font:700 1rem/1.25 "IBM Plex Sans",sans-serif}

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

.detail .shot{position:relative;aspect-ratio:4/3;background:var(--info-tint)}
.detail .shot img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.detail .shot.kosong{display:grid;place-items:center}
.detail .shot.kosong img{display:none}
.detail .shot .art{width:5.5rem;height:5.5rem;color:var(--blue);opacity:.5;stroke-width:1.2}
.detail .shot:not(.kosong) .art{display:none}
.detail .body{padding:1.5rem 1rem 0}
.detail .body h2{font-size:1.625rem}
.detail .body p{margin-top:1rem;font-size:1.0625rem;line-height:1.6}
.detail .fact{margin-top:1.5rem;padding:1.25rem;border-radius:1.25rem;background:var(--info-tint)}
.detail .fact dt{font:600 .8125rem/1 "IBM Plex Sans",sans-serif;letter-spacing:.1em;
  text-transform:uppercase;color:var(--blue-deep)}
.detail .fact dd{margin:.5rem 0 0;font-size:1.0625rem;line-height:1.5}

.cta{
  display:flex;align-items:center;justify-content:center;gap:.5rem;
  min-height:4rem;margin-top:1.5rem;padding:1rem 1.5rem;
  border-radius:var(--r-pill);background:var(--blue);color:var(--on-accent);
  font:600 1.0625rem/1 "IBM Plex Sans",sans-serif;
}
.cta .ic{width:1.25rem;height:1.25rem}
.cta--wa{background:var(--green)}

/* A whole article. The picture keeps its own proportions: most of the RW's
   article images are posters and letters, and a crop cuts their text. */
.artikel .body{padding-top:1.25rem}
.artikel .judul{font-size:1.625rem;line-height:1.2}
.artikel .byline{margin-top:.5rem;font-size:.9375rem;color:var(--ink-muted)}
.artikel .lead{width:100%;height:auto;margin-top:1.25rem;border-radius:1.25rem;background:var(--info-tint)}
.artikel .isi h3{margin-top:1.75rem;font-size:1.1875rem;line-height:1.3}
.artikel .isi h4{margin-top:1.25rem;font:700 1.0625rem/1.35 "IBM Plex Sans",sans-serif;letter-spacing:0}
.artikel .isi p{margin-top:1rem;font-size:1.0625rem;line-height:1.65}
.artikel .isi ul{margin-top:.75rem;padding-left:1.375rem;list-style:disc}
.artikel .isi li{margin-top:.375rem;font-size:1.0625rem;line-height:1.55}
.artikel .isi li::marker{color:var(--blue)}
.artikel .isi blockquote{margin:1.25rem 0 0;padding:.875rem 1rem;border-radius:1rem;
  background:var(--info-tint);font-size:1.0625rem;line-height:1.6;font-style:italic}

/* ---------------------------------------------------------------- notif */

.notif-group{margin-top:1.5rem}
.notif-group>h2{font:600 .75rem/1 "IBM Plex Sans",sans-serif;letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink-muted)}
.notif{display:flex;gap:1rem;margin-top:.75rem;padding:1rem;border-radius:1.25rem;
  background:var(--surface);border:1px solid var(--app-line)}
.notif--new{background:var(--warn-tint);border-color:color-mix(in srgb,var(--warn) 24%,transparent)}
.notif .tile{width:2.75rem;height:2.75rem;border-radius:.75rem}
.notif .tile .ic{width:1.375rem;height:1.375rem}
.notif .txt{flex:1;min-width:0}
.notif .txt .hd{display:flex;align-items:baseline;justify-content:space-between;gap:.75rem}
.notif .txt b{min-width:0;font:700 1rem/1.3 "IBM Plex Sans",sans-serif}
.notif .txt time{font-size:.8125rem;color:var(--ink-muted);white-space:nowrap}
.notif .txt p{margin-top:.375rem;font-size:.9375rem;line-height:1.5;color:var(--ink-muted)}

/* ---------------------------------------------------------------- sheet */

/* The recording conversation. A centred card over a scrim, matching the
   design the intern drew. It is a dialog: focus is trapped, Escape closes,
   and the page behind does not scroll. */
.scrim{
  position:fixed;inset:0;z-index:40;
  background:rgba(var(--scrim-rgb),.55);
  backdrop-filter:blur(2px);
  display:grid;place-items:center;padding:1rem;
  opacity:0;transition:opacity .2s var(--ease-state);
}
.scrim[hidden]{display:none}
.scrim.open{opacity:1}
body.locked{overflow:hidden}

.sheet{
  width:100%;max-width:22.5rem;max-height:86vh;max-height:86dvh;
  display:flex;flex-direction:column;
  background:var(--surface);border-radius:1.5rem;box-shadow:var(--sh3);
  transform:translateY(.75rem) scale(.98);
  transition:transform .22s var(--ease-in);
}
.scrim.open .sheet{transform:none}
/* Focus target for the dialog, not a control, so no visible ring. */
.sheet:focus,.sheet:focus-visible{outline:none}

.sheet .hd{display:flex;align-items:center;gap:1rem;padding:1.25rem 1.25rem 1rem}
.sheet .hd h2{flex:1;font-size:1.1875rem}
.sheet .hd .iconbtn{width:2.25rem;height:2.25rem;border-color:transparent;background:var(--app-bg)}
.sheet .hd .iconbtn .ic{width:1.125rem;height:1.125rem}

.log{flex:1;overflow-y:auto;padding:0 1.25rem;display:flex;flex-direction:column;gap:.625rem;
  -webkit-overflow-scrolling:touch}
.msg{max-width:85%;padding:.75rem 1rem;border-radius:1.125rem;font-size:1.0625rem;line-height:1.5}
.msg.bot{align-self:flex-start;background:var(--bg-soft);border-bottom-left-radius:.375rem}
.msg.me{align-self:flex-end;background:var(--blue);color:var(--on-accent);border-bottom-right-radius:.375rem}

#input{padding:1rem 1.25rem 1.25rem}
.answers{display:flex;flex-wrap:wrap;gap:.5rem}
.ans{
  min-height:3.25rem;padding:.75rem 1.25rem;border-radius:var(--r-pill);
  background:var(--surface);border:1.5px solid var(--line);color:var(--ink);
  font:600 1rem/1 "IBM Plex Sans",sans-serif;
  transition:background .18s var(--ease-state);
}
.ans:not(.ans--main):hover{background:color-mix(in srgb,var(--blue) 8%,var(--surface))}
.ans--main{background:var(--blue);border-color:var(--blue);color:var(--on-accent)}
.ans--main:hover{background:var(--blue-deep);color:var(--on-accent)}
.ans--quiet{border-color:transparent;color:var(--ink-muted)}

/* 72px stepper. No numeric field anywhere in this flow, by design: a keypad
   on a 5 inch screen is where this kind of app dies. */
.stepper{display:flex;align-items:center;gap:1rem;margin-bottom:.75rem;
  padding:1rem;border-radius:1.25rem;background:var(--app-bg);border:1px solid var(--app-line)}
.stepper button{
  width:4.5rem;height:4.5rem;flex:none;border-radius:1rem;
  background:var(--surface);border:1.5px solid var(--line);color:var(--blue);
  font:400 2rem/1 "IBM Plex Sans",sans-serif;
  display:grid;place-items:center;
  transition:background .18s var(--ease-state);
}
.stepper button:hover{background:var(--info-tint)}
.stepper button:active{background:var(--blue);color:var(--on-accent)}
.stepper .val{flex:1;min-width:0;text-align:center}
.stepper .val b{display:block;font:800 2.25rem/1 "Montserrat",sans-serif;
  font-variant-numeric:tabular-nums;letter-spacing:-.02em}
.stepper .val span{display:block;margin-top:.25rem;font-size:.875rem;color:var(--ink-muted)}
.stepper-help{margin-bottom:1rem;font-size:.9375rem;line-height:1.45;color:var(--ink-muted)}

/* ---------------------------------------------------------------- photos */

/* Every scraped photo sits in its own aspect box and is absolutely placed
   inside it. A percentage height on the img would be cyclic and silently
   disable object-fit. */
.foto{position:relative;display:block;overflow:hidden;background:var(--info-tint)}
.foto img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.foto--utuh img{object-fit:contain}

.sec-link{display:inline-flex;align-items:center;gap:.25rem;min-height:2.75rem;
  font:600 .9375rem/1 "IBM Plex Sans",sans-serif;color:var(--blue)}
.sec-link .ic{width:1.125rem;height:1.125rem}
.h-sub{margin-top:2rem;font-size:1.25rem}

/* A sideways row that bleeds to the screen edge, so the cut-off card on the
   right says "there is more" without an arrow. */
.strip,.galeri{display:flex;gap:.75rem;margin:1rem -1rem 0;padding:0 1rem .25rem;
  overflow-x:auto;scroll-snap-type:x mandatory;scroll-padding:0 1rem;
  -webkit-overflow-scrolling:touch;scrollbar-width:none}
.strip::-webkit-scrollbar,.galeri::-webkit-scrollbar{display:none}
.strip>*,.galeri>*{scroll-snap-align:start}

.gal{flex:none;width:14rem;margin:0}
.galeri--lebar .gal{width:16.5rem}
.gal .foto{aspect-ratio:4/3;border-radius:1.125rem}
.gal .ket{display:block;margin-top:.5rem;font-size:.9375rem;line-height:1.35;font-weight:600}
a.gal .ket{color:var(--blue)}

/* UMKM has no photo of its own: four of its members' products, as tiles. */
.shot.mozaik{display:grid;grid-template-columns:1fr 1fr;grid-template-rows:1fr 1fr;gap:2px}
.shot.mozaik .foto{position:relative;width:100%;height:100%}
.detail .shot.mozaik img,.proyek .shot.mozaik img{position:absolute}

/* ---------------------------------------------------------------- pasar */

.chips{display:flex;gap:.5rem;margin:1.5rem -1rem 0;padding:0 1rem .25rem;overflow-x:auto;scrollbar-width:none}
.chips::-webkit-scrollbar{display:none}
.chip{flex:none;display:inline-flex;align-items:center;gap:.5rem;min-height:2.75rem;padding:0 1rem;
  border-radius:var(--r-pill);font:600 .9375rem/1 "IBM Plex Sans",sans-serif;color:var(--ink)}
.chip .n{font-weight:500;font-size:.8125rem;color:var(--ink-muted)}
.chip[aria-pressed="true"]{background:var(--blue);color:var(--on-accent)}
.chip[aria-pressed="true"] .n{color:inherit;opacity:.8}

.sorot{position:relative;display:block;margin-top:1.5rem;border-radius:1.5rem;overflow:hidden;box-shadow:var(--sh2)}
.sorot .foto{aspect-ratio:16/11}
.sorot-kaca{position:absolute;left:.75rem;bottom:.75rem;right:.75rem;display:grid;gap:.25rem;
  padding:.875rem 1rem;border-radius:1.125rem}
.sorot-kaca b{font:700 1.125rem/1.25 "Montserrat",sans-serif}

.grid-barang{display:grid;grid-template-columns:1fr 1fr;gap:.75rem;margin-top:1rem}
.grid-barang .barang[hidden]{display:none}

.barang{display:flex;flex-direction:column;overflow:hidden;border-radius:1.25rem;
  transition:transform .18s var(--ease-state)}
.barang:active{transform:scale(.98)}
.barang .foto{aspect-ratio:1/1}
.barang .txt{display:flex;flex-direction:column;gap:.25rem;flex:1;padding:.75rem .875rem .875rem}
.barang .penjual{font-size:.75rem;color:var(--ink-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.barang b{font:700 .9375rem/1.3 "IBM Plex Sans",sans-serif;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.barang .harga{margin-top:auto;padding-top:.25rem;font:800 1rem/1.2 "Montserrat",sans-serif;
  font-variant-numeric:tabular-nums;color:var(--blue-deep)}
.barang .harga small{display:block;margin-top:.1875rem;font:500 .75rem/1.2 "IBM Plex Sans",sans-serif;color:var(--ink-muted)}
.barang--mini{flex:none;width:10.5rem}

/* ---------------------------------------------------------------- produk */

.produk .body h2:first-of-type{margin-top:.375rem}
.detail .body .h-sub{margin-top:2rem;font-size:1.25rem}
.detail .body .harga-besar{margin-top:.75rem;display:flex;align-items:baseline;gap:.375rem}
.harga-besar b{font:800 2rem/1 "Montserrat",sans-serif;font-variant-numeric:tabular-nums;color:var(--blue-deep)}
.harga-besar span{font-size:1rem;color:var(--ink-muted)}

.beli{margin-top:1.5rem;padding:1.25rem;border-radius:1.25rem}
.varian{margin:0;padding:0;border:0;display:grid;grid-template-columns:1fr 1fr;gap:.5rem}
.varian legend{padding:0;margin-bottom:.625rem;font:600 .8125rem/1 "IBM Plex Sans",sans-serif;
  letter-spacing:.1em;text-transform:uppercase;color:var(--ink-muted)}
.opsi{position:relative;display:flex}
.opsi input{position:absolute;opacity:0;width:1px;height:1px}
.opsi span{flex:1;display:grid;gap:.25rem;min-height:3.5rem;padding:.625rem .75rem;border-radius:.875rem;
  border:1.5px solid var(--app-line);background:var(--surface);cursor:pointer;
  transition:border-color .18s var(--ease-state),background .18s var(--ease-state)}
.opsi b{font:600 .9375rem/1.25 "IBM Plex Sans",sans-serif}
.opsi small{font-size:.8125rem;color:var(--ink-muted);font-variant-numeric:tabular-nums}
.opsi input:checked+span{border-color:var(--blue);background:var(--info-tint)}
.opsi input:focus-visible+span{outline:3px solid var(--blue);outline-offset:2px}
.detail .beli .isi{margin:0;font-size:1rem;font-weight:600}

.jumlah{display:flex;align-items:center;justify-content:space-between;gap:1rem;margin-top:1.25rem}
.jumlah .lbl{font-weight:600}
.qty{display:flex;align-items:center;gap:.25rem;padding:.25rem;border-radius:var(--r-pill);
  background:var(--app-bg);border:1px solid var(--app-line)}
.qty button{width:3rem;height:3rem;border-radius:50%;display:grid;place-items:center;
  font:500 1.5rem/1 "IBM Plex Sans",sans-serif;color:var(--blue);background:var(--surface);
  border:1px solid var(--app-line);transition:transform .16s var(--ease-state)}
.qty button:active{transform:scale(.94)}
.qty button:disabled{color:var(--ink-muted);opacity:.5}
.qty output{min-width:2.5rem;text-align:center;font:800 1.375rem/1 "Montserrat",sans-serif;font-variant-numeric:tabular-nums}

.total{display:flex;align-items:baseline;justify-content:space-between;gap:1rem;margin-top:1.25rem;
  padding-top:1rem;border-top:1px solid var(--app-line)}
.total span{color:var(--ink-muted);font-size:.9375rem}
.total b{font:800 1.375rem/1 "Montserrat",sans-serif;font-variant-numeric:tabular-nums}
.beli .cta{margin-top:1.25rem}

.info{margin:1.5rem 0 0;display:grid;gap:0;border-radius:1.25rem;overflow:hidden}
.info div{display:flex;justify-content:space-between;gap:1rem;padding:.875rem 1rem;border-bottom:1px solid var(--app-line)}
.info div:last-child{border-bottom:0}
.info dt{color:var(--ink-muted);font-size:.9375rem}
.info dd{margin:0;text-align:right;font-weight:600;font-size:.9375rem}

/* ---------------------------------------------------------------- motion */

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.01ms !important;transition-duration:.01ms !important}
}
