/* Shared styling for the public landing pages.
   Token values and the visual language are taken from the member directory so
   the new pages belong to the same site: 18px cards, 22px panels, 10px buttons,
   99px pills, one-pixel borders, and the same navy-to-green gradient.
   External on purpose -- these pages carry no inline CSS or JS, which is what
   lets the CSP tighten later. */

:root{
  --navy:#0E4A66; --teal:#0F8270; --emerald:#0DA055; --mint:#14B886; --mint-2:#5FD3A9;
  --bg:#F6F8F7; --surface:#FFFFFF; --surface-2:#EEF2F1;
  --ink:#0A1F2E; --ink-2:#3F525E; --ink-3:#6B7C86;
  --line:#E4EAE8; --line-2:#D2DCD9;
  --chip-max-bg:#0E4A66; --chip-max-fg:#FFFFFF; --chip-pro:#0E4A66;
  --tab-on-bg:#0E4A66; --tab-on-fg:#FFFFFF;
  --foot-bg:#0A1F2E; --foot-fg:#FFFFFF; --foot-2:#A8B8C0;
  --grad:linear-gradient(90deg,#0E4A66 0%,#0F8270 50%,#0DA055 100%);
  --accent:#0F8270;
  --primary:#0F8270; --primary-fg:#FFFFFF;
  --scrim:255,255,255;
  --tint-1:20,184,134; --tint-2:14,74,102; --tint-3:13,160,85;
  --ok-bg:rgba(13,160,85,.12); --ok-fg:#0A7A43;
  --warn-bg:rgba(224,122,43,.14); --warn-fg:#9A5316;
  --danger-fg:#B42318; --danger-bg:rgba(180,35,24,.08);
  --sans:"Geist",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
  --mono:"Geist Mono",ui-monospace,"SFMono-Regular",Menlo,monospace;
}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]){
    color-scheme:dark;
    --bg:#0A1F2E; --surface:#0F2A3D; --surface-2:#153549;
    --ink:#EAF1F3; --ink-2:#B6C5CC; --ink-3:#8699A3;
    --line:#1E3A4E; --line-2:#2A4A60;
    --chip-max-bg:#5FD3A9; --chip-max-fg:#0A1F2E; --chip-pro:#5FD3A9;
    --tab-on-bg:#5FD3A9; --tab-on-fg:#0A1F2E;
    --foot-bg:#061520;
    --grad:linear-gradient(90deg,#5FD3A9 0%,#44C9A4 50%,#2DAEA7 100%);
    --accent:#5FD3A9; --primary:#5FD3A9; --primary-fg:#0A1F2E;
    --scrim:15,42,61;
    --tint-1:95,211,169; --tint-2:45,174,167; --tint-3:14,130,112;
    --ok-bg:rgba(95,211,169,.16); --ok-fg:#5FD3A9;
    --warn-bg:rgba(224,122,43,.18); --warn-fg:#E8A96A;
    --danger-fg:#F59B92; --danger-bg:rgba(245,155,146,.12);
    --navy:#7FC3DF;
  }
}
:root[data-theme="dark"]{
  color-scheme:dark;
  --bg:#0A1F2E; --surface:#0F2A3D; --surface-2:#153549;
  --ink:#EAF1F3; --ink-2:#B6C5CC; --ink-3:#8699A3;
  --line:#1E3A4E; --line-2:#2A4A60;
  --chip-max-bg:#5FD3A9; --chip-max-fg:#0A1F2E; --chip-pro:#5FD3A9;
  --tab-on-bg:#5FD3A9; --tab-on-fg:#0A1F2E;
  --foot-bg:#061520;
  --grad:linear-gradient(90deg,#5FD3A9 0%,#44C9A4 50%,#2DAEA7 100%);
  --accent:#5FD3A9; --primary:#5FD3A9; --primary-fg:#0A1F2E;
  --scrim:15,42,61;
  --tint-1:95,211,169; --tint-2:45,174,167; --tint-3:14,130,112;
  --ok-bg:rgba(95,211,169,.16); --ok-fg:#5FD3A9;
  --warn-bg:rgba(224,122,43,.18); --warn-fg:#E8A96A;
  --danger-fg:#F59B92; --danger-bg:rgba(245,155,146,.12);
  --navy:#7FC3DF;
}

*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--ink);font-family:var(--sans);font-size:16px;line-height:1.55;-webkit-font-smoothing:antialiased;letter-spacing:-.005em}
img{max-width:100%;height:auto}
.wrap{max-width:1120px;margin:0 auto;padding-inline:clamp(16px,4vw,48px)}
:focus-visible{outline:2px solid var(--teal);outline-offset:2px}
.skip{position:absolute;left:-9999px}
.skip:focus{left:16px;top:16px;z-index:50;background:var(--surface);padding:10px 14px;border-radius:10px;border:1px solid var(--line-2)}

/* ---- top bar + nav ---- */
.topbar{background:var(--surface);border-bottom:1px solid var(--line);position:sticky;top:0;z-index:20}
.topbar .wrap{min-height:60px;display:flex;align-items:center;gap:16px;padding-block:10px}
/* The pages without a photograph get a colour wash instead of a flat panel. The
   same sweep as the landing page's picture but running the other way: strongest
   where the heading is and clear across the empty half, so the colour sits under
   the words rather than in the space beside them. Stops are the mirror of the
   original -- 100, 68, 38 became 0, 32, 62 along the same axis. The triplets are
   per theme, so it warms a light page and glows behind a dark one. The veil is
   switched off here: it exists to hold a photograph back from the headline, and
   against something this quiet it would only mute the left third for nothing. */
body[data-page="spesifikasi"] .hero,
body[data-page="acara"] .hero,
body[data-page="tips"] .hero,
body[data-page="bergabung"] .hero{
  background-image:linear-gradient(105deg,
    rgba(var(--tint-2),.34) 0%, rgba(var(--tint-1),.20) 32%, transparent 62%)}
body[data-page="spesifikasi"] .hero::before,
body[data-page="acara"] .hero::before,
body[data-page="tips"] .hero::before,
body[data-page="bergabung"] .hero::before{content:none}

/* Landing page only: the hero picture carries on up behind the bar, so the bar
   has no surface of its own while it sits over the image. The gradient keeps the
   menu readable against whatever is underneath; once the hero has scrolled past,
   the bar takes its surface back, because otherwise the page would slide under
   an invisible strip. --bar-h is measured in site.js -- the bar gains a row on
   narrow screens, so it cannot be a constant. */
body[data-page="beranda"] .topbar{background:transparent;border-bottom-color:transparent;
  transition:background-color .25s ease,border-color .25s ease}
/* Reaches below the bar and starts heavier: the picture underneath can be a
   bright sky, and at the old weight the menu washed out against it. Falling off
   past the bar's own edge keeps it from reading as a band. */
body[data-page="beranda"] .topbar::after{content:"";position:absolute;inset:0 0 -44px 0;z-index:-1;
  pointer-events:none;transition:opacity .25s ease;
  background:linear-gradient(180deg,rgba(var(--scrim),.92) 0%,rgba(var(--scrim),.78) 45%,
    rgba(var(--scrim),.38) 72%,rgba(var(--scrim),0) 100%)}
body[data-page="beranda"] .topbar.solid{background:var(--surface);border-bottom-color:var(--line)}
body[data-page="beranda"] .topbar.solid::after{opacity:0}
body[data-page="beranda"] .hero{margin-top:calc(-1 * var(--bar-h,65px));
  padding-top:calc(var(--bar-h,65px) + clamp(36px,7vw,72px))}
.brand{display:flex;align-items:center;gap:12px;font-size:13px;font-weight:600;color:var(--ink);text-decoration:none;flex-shrink:0}
.brand .div{width:1px;height:18px;background:var(--line-2)}
.brand span:last-child{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:500;color:var(--ink-2)}
.nav{display:flex;align-items:center;gap:4px;margin-left:auto;flex-wrap:wrap;position:relative}
.nav a{position:relative;z-index:1;padding:8px 12px 13px;font-size:14px;font-weight:500;color:var(--ink-2);text-decoration:none;white-space:nowrap}
.nav a:hover{color:var(--ink)}
.nav a[aria-current="page"]{color:var(--ink);font-weight:600}
/* The current page is underlined in the brand gradient. The bar is inset by the
   link's own horizontal padding so it sits under the label rather than spanning
   the whole hit area; the extra bottom padding is the room it lives in, and the
   bar keeps its height because the 44px theme button, not the links, sets it. */
.nav a[aria-current="page"]::after{content:"";position:absolute;left:12px;right:12px;bottom:4px;
  height:3px;border-radius:99px;background:var(--grad)}
/* One highlight travels between the items instead of blinking out here and back
   in somewhere else. Tint and shadow are both mixed from --accent, so they
   follow the theme without a second set of tokens; the flat rgba lines above
   each are the fallback where color-mix is not understood. */
.nav-glow{position:absolute;left:0;top:0;z-index:0;border-radius:10px;pointer-events:none;opacity:0;
  background:rgba(127,127,127,.12);
  background:color-mix(in srgb, var(--accent) 13%, transparent);
  box-shadow:0 2px 14px rgba(127,127,127,.2), inset 0 0 0 1px rgba(127,127,127,.22);
  box-shadow:0 2px 14px color-mix(in srgb, var(--accent) 26%, transparent),
             inset 0 0 0 1px color-mix(in srgb, var(--accent) 30%, transparent);
  transition:transform .3s cubic-bezier(.34,.08,.2,1),width .3s cubic-bezier(.34,.08,.2,1),
             height .3s cubic-bezier(.34,.08,.2,1),opacity .2s ease}
.nav-glow.on{opacity:1}
@media (prefers-reduced-motion:reduce){.nav-glow{transition:opacity .2s ease}}
/* Entrance animation. The starting state is set by a class that /boot.js adds in
   <head>, so these are hidden from the very first frame and only ever revealed --
   never painted solid and then hidden, which is what flickered before. The curve
   is the one the reference site uses for its own transitions. Restrained on
   purpose: a short fade and a few pixels of lift, no per-line choreography. */
@keyframes fadeUp{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
.anim .hero .wrap > *{opacity:0}
.anim .hero.in .wrap > *{animation:fadeUp .4s cubic-bezier(.4,0,.2,1) both;animation-delay:var(--d,0s)}
.anim main section.below{opacity:0}
.anim main section.below.in{animation:fadeUp .35s cubic-bezier(.4,0,.2,1) both}
@keyframes lift{from{transform:translateY(110%)}to{transform:none}}
.anim .hero .masked{display:block;overflow:hidden;padding-bottom:.1em;margin-bottom:-.1em}
.anim .hero .masked > .ink{display:block;transform:translateY(110%)}
.anim .hero.in .masked > .ink{animation:lift .95s cubic-bezier(.34,.02,.2,1) both;
  animation-delay:var(--d,0s)}
/* The heading does not fade -- its lines arrive by climbing, and a fade on top
   would soften the very edge they climb out of. It only needs its opacity back,
   which shows nothing until a line has moved. */
.anim .hero.in .wrap h1{opacity:1;animation:none}
/* 44px, matching the directory and the 44px touch target the rest of the site
   uses for .tab and the search field. */
/* Header buttons look 36px but keep a 44px box: the box is the tap target (and the
   slot the header reserves), the visible ring is drawn 4px inside it. */
.theme-btn{position:relative;width:44px;height:44px;display:grid;place-items:center;padding:0;border:0;border-radius:12px;background:none;color:var(--ink);cursor:pointer;flex-shrink:0}
.theme-btn::before{content:"";position:absolute;inset:4px;border-radius:10px;border:1px solid var(--line);background:var(--surface);transition:border-color .15s}
.theme-btn:hover::before{border-color:var(--ink-3)}
.theme-btn svg{position:relative;width:16px;height:16px}
.theme-btn .i-sun{display:none}
.theme-btn.is-dark .i-sun{display:block}
.theme-btn.is-dark .i-moon{display:none}
/* The account slot (/me.js), just before the theme button: the member's initial
   when signed in, a "Masuk" person icon otherwise. It is always there, so until the
   deferred script has filled it the theme button stands 54px further right (44px
   circle + 10px gap) and the bar never moves. The brand gives way on very narrow
   screens (320px) instead of pushing the theme button onto a row of its own. Public
   pages only (body[data-page]): the admin shares this stylesheet but not me.js. */
body[data-page] .topbar .theme-btn{margin-left:54px}
body[data-page] .topbar .me + .theme-btn{margin-left:0}
/* With the battery button the reserved slot moves in front of it: account, battery, theme. */
body[data-page] .topbar .calc-btn{margin-left:54px}
body[data-page] .topbar .me + .calc-btn{margin-left:0}
/* Battery and theme sit as a pair: the boxes touch, so the visible rings are 8px apart (the bar's gap is 16). */
body[data-page] .topbar .calc-btn + .theme-btn{margin-left:-16px}
body[data-page] .topbar .brand{max-width:calc(100% - 168px);min-width:0;flex-shrink:1}
body[data-page] .topbar .brand svg{flex-shrink:0}
.me{position:relative;flex-shrink:0;margin-right:-6px}
/* Battery calculator (/calc.js builds the popup and carries its styles). The button is
   static HTML right before the theme button; me.js puts the account slot in front of it
   (order: account, battery, theme), and the slot is reserved, so the bar never moves. */
.calc-btn{position:relative;width:44px;height:44px;display:grid;place-items:center;padding:0;border:0;border-radius:12px;background:none;color:var(--ink);cursor:pointer;flex-shrink:0}
.calc-btn::before{content:"";position:absolute;inset:4px;border-radius:10px;border:1px solid var(--line);background:var(--surface);transition:border-color .15s}
.calc-btn:hover::before{border-color:var(--ink-3)}
.calc-btn svg{position:relative;width:16px;height:16px}
.calc-btn:focus-visible{outline:2px solid var(--teal);outline-offset:-2px}
.me-btn,.me-in{position:relative;width:44px;height:44px;display:grid;place-items:center;padding:0;border:0;border-radius:50%;background:none;color:var(--ink);cursor:pointer;font:inherit}
.me-btn::before,.me-in::before{content:"";position:absolute;inset:4px;border-radius:50%;border:1px solid var(--line);background:var(--surface);transition:border-color .15s}
.me-btn:hover::before,.me-btn[aria-expanded="true"]::before,.me-in:hover::before{border-color:var(--ink-3)}
.me-in svg{position:relative;width:16px;height:16px}
.me-in:focus-visible,.me-btn:focus-visible,.theme-btn:focus-visible{outline:2px solid var(--teal);outline-offset:-2px}
.me-ini{position:relative;width:30px;height:30px;display:grid;place-items:center;border-radius:50%;background:var(--accent);color:var(--surface);font-size:14px;font-weight:600;line-height:1}
.me-ini svg{width:16px;height:16px}
.me-menu{position:absolute;right:0;top:calc(100% + 8px);z-index:30;width:248px;max-width:calc(100vw - 32px);padding:6px;background:var(--surface);border:1px solid var(--line-2);border-radius:14px;box-shadow:0 12px 32px rgba(10,31,46,.16)}
.me-menu[hidden]{display:none}
.me-who{margin:0 0 6px;padding:8px 10px 12px;border-bottom:1px solid var(--line);display:flex;align-items:center;gap:12px;line-height:1.3}
.me-av{flex:none;width:40px;height:40px;display:grid;place-items:center;border-radius:50%;background:var(--accent);color:var(--surface);font-size:17px;font-weight:600}
.me-av svg{width:18px;height:18px}
.me-txt{display:flex;flex-direction:column;align-items:flex-start;gap:4px;min-width:0}
.me-who b{max-width:100%;font-size:15px;font-weight:600;color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.me-role{font-size:12px;font-weight:500;color:var(--ink-3)}
.me-role.is-admin{display:inline-flex;align-items:center;padding:2px 8px;border-radius:999px;background:var(--surface-2);color:var(--navy);font-weight:600}
.me-role.is-admin::before{content:"";width:6px;height:6px;margin-right:6px;border-radius:2px;background:currentColor;transform:rotate(45deg)}
.me-menu ul{list-style:none;margin:0;padding:0}
.me-menu li.me-sep{margin-top:6px;padding-top:6px;border-top:1px solid var(--line)}
.me-menu li[hidden]{display:none}
.me-menu a,.me-menu button{display:flex;align-items:center;gap:12px;width:100%;min-height:44px;padding:0 12px;border:0;border-radius:10px;background:none;color:var(--ink);font:inherit;font-size:14.5px;font-weight:500;text-align:left;text-decoration:none;cursor:pointer}
.me-menu .me-ico{flex:none;color:var(--ink-3)}
.me-menu .me-label{flex:1;min-width:0}
.me-menu a:hover,.me-menu button:hover{background:var(--surface-2)}
.me-menu a:hover .me-ico{color:var(--ink)}
.me-menu .me-adm,.me-menu .me-adm .me-ico{color:var(--navy)}
.me-menu .me-go{flex:none;opacity:.7;transition:transform .15s}
.me-menu .me-adm:hover .me-go{transform:translateX(2px);opacity:1}
.me-menu .me-out,.me-menu .me-out .me-ico{color:var(--danger-fg)}
.me-menu .me-out:hover{background:var(--danger-bg)}
.me-menu :focus-visible{outline-offset:-2px}
.me-menu button:disabled{cursor:default;color:var(--ink-3)}
.me-menu button:disabled .me-ico{color:var(--ink-3)}
.maint-screen{min-height:70vh;display:grid;place-items:center;text-align:center}
.maint-screen .lede{margin:12px auto 0}
.maint-screen .hint{margin:10px auto 0}
@media (prefers-reduced-motion:reduce){*{transition:none!important}}
@media (max-width:760px){
  .topbar .wrap{flex-wrap:wrap}
  .nav{order:3;width:100%;margin-left:0;overflow-x:auto;padding-bottom:2px}
}
/* The public menu (six items) no longer fits beside the name below about 845px;
   wrapped there it made a two-line bar next to the logo. So on the public pages it
   takes its own row from 860 down -- the same break the member directory's own
   copy of these rules already used. Admin pages (no data-page) keep 760. */
@media (max-width:860px){
  body[data-page] .topbar .wrap{flex-wrap:wrap}
  body[data-page] .nav{order:3;width:100%;margin-left:0;overflow-x:auto;padding-bottom:2px}
}

/* ---- page furniture ---- */
.hero{background:var(--surface);border-bottom:1px solid var(--line);padding-block:clamp(36px,7vw,72px);position:relative;overflow:hidden}
/* Backdrop image, faded out on the left so the headline never fights it and
   strongest on the right where the column of text has already ended. */
.hero::after{
  content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  background-image:var(--hero-bg,none);background-size:cover;background-position:right var(--hero-y,50%);background-repeat:no-repeat;
  /* The three stops and the strength are set from the CMS, so the look can be
     tuned without a deploy. Defaults match what is configured today. */
  -webkit-mask-image:linear-gradient(90deg,transparent 0%,transparent var(--hero-fade,40%),rgba(0,0,0,.35) var(--hero-ramp1,60%),#000 var(--hero-ramp2,90%),#000 100%);
  mask-image:linear-gradient(90deg,transparent 0%,transparent var(--hero-fade,40%),rgba(0,0,0,.35) var(--hero-ramp1,60%),#000 var(--hero-ramp2,90%),#000 100%);
  opacity:var(--hero-strength,.9)}
/* The picture runs at full strength now, which put the second line of the lede
   on top of the green car. This sits between the image and the text and covers
   only the column the text occupies -- it is gone by two-thirds across, so the
   right-hand side is exactly as strong as before. */
/* The admin preview shares this declaration rather than copying it. It was
   reproducing the picture and its mask but not this layer, so the left-hand side
   looked far stronger there than on the page, where it is dimmed twice. One rule
   means the two cannot drift apart again. */
.hero::before,
.heroprev .frame::before{
  content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
  /* The peak is set from the CMS; the later stops are held in the proportions the
     hand-tuned gradient had, so lowering it thins the whole veil evenly instead
     of only its left edge. */
  background:linear-gradient(90deg,
    rgba(var(--scrim),calc(var(--scrim-a,.92) * 1)) 0%,
    rgba(var(--scrim),calc(var(--scrim-a,.92) * .924)) 30%,
    rgba(var(--scrim),calc(var(--scrim-a,.92) * .435)) 52%,
    rgba(var(--scrim),0) 70%)}
.hero .wrap{position:relative;z-index:2}
/* The supplied artwork is a dusk scene, so it sits darker than a light page and
   lighter than a dark one. Each theme gets the weight that keeps text readable. */
/* Narrow screens: the picture stops being a backdrop and becomes a block under the
   buttons, as wide as the screen and as tall as its own proportions, so none of it
   is cropped and no veil is needed behind the text. */
@media (max-width:760px){
  .hero.has-pic{padding-bottom:0}
  .hero.has-pic::before{content:none}
  .hero.has-pic::after{position:relative;inset:auto;display:block;width:100%;margin-top:28px;
    aspect-ratio:var(--hero-ratio,1.66);background-size:contain;background-position:center bottom;
    -webkit-mask-image:linear-gradient(180deg,transparent 0%,#000 30%,#000 100%);
    mask-image:linear-gradient(180deg,transparent 0%,#000 30%,#000 100%);
    opacity:var(--hero-strength,.9)}
}
.hero .wrap{display:flex;flex-direction:column;gap:24px}
.kicker{align-self:flex-start;display:inline-flex;align-items:center;padding:4px 12px;background:var(--ok-bg);border-radius:99px;font-size:12px;font-weight:700;letter-spacing:.1em;color:var(--ok-fg)}
h1{margin:0;font-size:clamp(34px,5.6vw,60px);line-height:1.04;letter-spacing:-.03em;font-weight:700;text-wrap:balance}
h1 .line{display:block}
.grad{background:var(--grad);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent}
.lede{margin:0;font-size:clamp(16px,1.8vw,19px);color:var(--ink-2);max-width:60ch;white-space:pre-line}
/* The page is filled in by script, so before that arrives <main> is empty and the
   footer sits inside the viewport -- then content lands and shoves it off-screen.
   That single move measured CLS 0.574, well past the 0.25 Google calls poor.
   Holding the height keeps the footer below the fold from the first paint, so
   there is nothing visible left to move. */
main{min-height:80vh}
/* The footer waits with main. It is built by script from an empty div, so it
   painted as a short dark bar and then jumped to full height a tenth of a second
   later -- measured at 120px then 303px. Worst in the light theme, where that bar
   is navy against near-white and the jump is impossible to miss. Hiding rather
   than reserving a height, because the height depends on the content. */
/* A hero whose words are in the HTML (data-static: Acara, Spesifikasi, Bergabung, Tips) shows at once; only the
   parts that need the content request wait. The Tips list is static too: it holds skeleton cards until then. */
body[data-loading] main > :not([data-static]),body[data-loading] footer{opacity:0}
main > *,footer{transition:opacity .16s ease}
@media (prefers-reduced-motion:reduce){ main > *{transition:none} }
section{padding-block:clamp(32px,5vw,56px)}
section + section{border-top:1px solid var(--line)}
h2{margin:0 0 6px;font-size:clamp(22px,3vw,30px);line-height:1.15;letter-spacing:-.02em;font-weight:700}
.sec-head{margin-bottom:22px;display:flex;justify-content:space-between;align-items:flex-end;gap:16px;flex-wrap:wrap}
.sec-head p{margin:0;color:var(--ink-2);white-space:pre-line}

/* ---- buttons ---- */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:44px;padding:0 18px;border-radius:10px;border:1px solid var(--line-2);background:var(--surface);color:var(--ink);font:inherit;font-size:15px;font-weight:600;cursor:pointer;text-decoration:none;white-space:nowrap}
.btn:hover{border-color:var(--ink-3)}
.btn.primary{background:var(--primary);border-color:var(--primary);color:var(--primary-fg)}
.btn.primary:hover{filter:brightness(1.06)}
.btn.sm{min-height:34px;padding:0 12px;font-size:13px}
/* Buttons that stand together are one width (the widest label decides), and on a phone
   they stack at the full width -- never a short one next to or above a long one. */
.btn-row{display:grid;grid-auto-flow:column;grid-auto-columns:1fr;gap:10px;width:fit-content;max-width:100%}
@media (max-width:560px){ .btn-row{display:grid;grid-auto-flow:row;width:100%} }

/* ---- cards & grids ---- */
.grid{display:grid;gap:16px;grid-template-columns:repeat(auto-fill,minmax(260px,1fr))}
.card{background:var(--surface);border:1px solid var(--line);border-top:3px solid var(--c,var(--teal));border-radius:18px;padding:20px;display:flex;flex-direction:column;gap:10px}
.card h3{margin:0;font-size:17px;letter-spacing:-.01em}
.card p{margin:0;color:var(--ink-2);font-size:14.5px}
.empty{padding:40px 24px;text-align:center;color:var(--ink-3);border:1px dashed var(--line-2);border-radius:18px;white-space:pre-line}

/* ---- rendered markdown ---- */
.prose{color:var(--ink-2);max-width:70ch}
.prose > :first-child{margin-top:0}
.prose > :last-child{margin-bottom:0}
.prose p{margin:0 0 14px}
.prose h2{margin:28px 0 8px;color:var(--ink)}
.prose h3{margin:22px 0 6px;font-size:19px;color:var(--ink)}
.prose h4{margin:18px 0 6px;font-size:16.5px;color:var(--ink)}
.prose ul,.prose ol{margin:0 0 14px;padding-left:22px}
.prose li{margin-bottom:6px}
.prose strong{color:var(--ink)}
.prose a{color:var(--accent);text-underline-offset:3px}
.prose code{font-family:var(--mono);font-size:.9em;background:var(--surface-2);padding:1px 5px;border-radius:6px}
.prose blockquote{margin:0 0 14px;padding-left:14px;border-left:3px solid var(--line-2);color:var(--ink-3)}
.prose hr{border:0;border-top:1px solid var(--line);margin:22px 0}

/* ---- events ---- */
.event{background:var(--surface);border:1px solid var(--line);border-radius:18px;overflow:hidden;display:flex;flex-direction:column}
.event .shot{aspect-ratio:16/9;background:var(--surface-2);object-fit:cover;width:100%}
.event .body{padding:16px 18px 18px;display:flex;flex-direction:column;gap:8px}
.event h3{margin:0;font-size:17px;letter-spacing:-.01em}
.event .when{display:inline-flex;align-items:center;gap:8px;font-size:13px;font-weight:600;color:var(--accent)}
.event .where{font-size:13.5px;color:var(--ink-3)}
/* A finished event: the picture steps back, the text keeps full contrast (it is still read). */
.event.past .shot{opacity:.72}

/* ---- gallery ---- */
/* One fixed composition per photograph count; the landing shows at most eight.
   Every composition is a full rectangle, so no count leaves a hole. The box is
   twelve columns wide with two or three rows and keeps its ratio, so the shape
   scales with the page instead of reflowing as pictures load. The top band
   is always a large cell (5 columns), a pair stacked beside it (4) and a slim
   one (3); five to eight photographs add a strip underneath. */
.gal{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
  grid-auto-rows:clamp(96px,22vw,150px);gap:10px}
.gal .shot-btn{margin:0;padding:0;border:1px solid var(--line);border-radius:14px;overflow:hidden;
  background:var(--surface-2);position:relative;cursor:zoom-in;display:block;
  min-width:0;min-height:0;grid-row:span 2}
.gal .shot-btn:first-child,.gal .shot-btn:last-child:nth-child(even){grid-column:1/-1}
@media (min-width:721px){
  .gal{grid-template-columns:repeat(12,minmax(0,1fr));grid-template-rows:repeat(2,minmax(0,1fr));
    grid-auto-rows:auto;gap:clamp(8px,1.2vw,12px);aspect-ratio:3.3}
  .gal:is([data-n="7"],[data-n="8"]){grid-template-rows:repeat(3,minmax(0,1fr));aspect-ratio:2.17}
  .gal .shot-btn{grid-row:auto}
  /* 1 and 2: the photograph(s) alone */
  .gal[data-n="1"] .shot-btn:nth-child(1){grid-area:1/3/3/11}
  .gal[data-n="2"] .shot-btn:nth-child(1){grid-area:1/1/3/7}
  .gal[data-n="2"] .shot-btn:nth-child(2){grid-area:1/7/3/13}
  /* 3: large, medium, slim -- each the full height */
  .gal[data-n="3"] .shot-btn:nth-child(1){grid-area:1/1/3/6}
  .gal[data-n="3"] .shot-btn:nth-child(2){grid-area:1/6/3/10}
  .gal[data-n="3"] .shot-btn:nth-child(3){grid-area:1/10/3/13}
  /* 4, 7, 8: large, a stacked pair, slim */
  .gal:is([data-n="4"],[data-n="5"],[data-n="7"],[data-n="8"]) .shot-btn:nth-child(1){grid-area:1/1/3/6}
  .gal:is([data-n="4"],[data-n="5"],[data-n="7"],[data-n="8"]) .shot-btn:nth-child(2){grid-area:1/6/2/10}
  .gal:is([data-n="4"],[data-n="5"],[data-n="7"],[data-n="8"]) .shot-btn:nth-child(3){grid-area:2/6/3/10}
  .gal:is([data-n="4"],[data-n="7"],[data-n="8"]) .shot-btn:nth-child(4){grid-area:1/10/3/13}
  /* 5: the slim cell splits into a pair as well */
  .gal[data-n="5"] .shot-btn:nth-child(4){grid-area:1/10/2/13}
  .gal[data-n="5"] .shot-btn:nth-child(5){grid-area:2/10/3/13}
  /* 6: tall, pair, tall, pair */
  .gal[data-n="6"] .shot-btn:nth-child(1){grid-area:1/1/3/4}
  .gal[data-n="6"] .shot-btn:nth-child(2){grid-area:1/4/2/7}
  .gal[data-n="6"] .shot-btn:nth-child(3){grid-area:2/4/3/7}
  .gal[data-n="6"] .shot-btn:nth-child(4){grid-area:1/7/3/10}
  .gal[data-n="6"] .shot-btn:nth-child(5){grid-area:1/10/2/13}
  .gal[data-n="6"] .shot-btn:nth-child(6){grid-area:2/10/3/13}
  /* 7 and 8: the strip underneath */
  .gal[data-n="7"] .shot-btn:nth-child(5){grid-area:3/1/4/5}
  .gal[data-n="7"] .shot-btn:nth-child(6){grid-area:3/5/4/9}
  .gal[data-n="7"] .shot-btn:nth-child(7){grid-area:3/9/4/13}
  .gal[data-n="8"] .shot-btn:nth-child(5){grid-area:3/1/4/4}
  .gal[data-n="8"] .shot-btn:nth-child(6){grid-area:3/4/4/7}
  .gal[data-n="8"] .shot-btn:nth-child(7){grid-area:3/7/4/10}
  .gal[data-n="8"] .shot-btn:nth-child(8){grid-area:3/10/4/13}
}
/* The picture pushes in slightly under the pointer while the cell holds its size,
   so the mosaic never reflows -- the crop tightens instead. The frame lifts with
   it so the cell reads as raised rather than merely brighter. */
.gal img{display:block;width:100%;height:100%;object-fit:cover;
  transition:transform .45s cubic-bezier(.2,.6,.2,1)}
.gal .shot-btn{transition:border-color .25s ease,box-shadow .25s ease,transform .25s ease}
.gal .shot-btn:hover,.gal .shot-btn:focus-visible{border-color:var(--line-2);
  box-shadow:0 10px 26px rgba(0,0,0,.22);transform:translateY(-2px);z-index:1}
.gal .shot-btn:hover img,.gal .shot-btn:focus-visible img{transform:scale(1.07)}
.gal .cap{position:absolute;left:0;right:0;bottom:0;padding:18px 12px 9px;text-align:left;
  font-size:12px;line-height:1.35;color:#fff;opacity:0;transition:opacity .18s ease;
  background:linear-gradient(180deg,transparent,rgba(4,14,22,.85))}
.gal .shot-btn:hover .cap,.gal .shot-btn:focus-visible .cap{opacity:1}

/* ---- spec table ---- */
.spec{width:100%;border-collapse:collapse;font-size:14.5px}
.spec caption{text-align:left;color:var(--ink-3);font-size:13px;padding-bottom:10px;white-space:pre-line}
.spec th,.spec td{padding:10px 12px;border-bottom:1px solid var(--line);vertical-align:top;text-align:left}
.spec thead th{font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3);font-weight:600;white-space:nowrap}
.spec tbody th{font-weight:500;color:var(--ink-2);width:34%}
.spec tr.diff td,.spec tr.diff th{background:var(--surface)}
.spec .none{color:var(--ink-3);font-style:italic}
.src{display:inline-block;margin-left:6px;padding:1px 7px;border-radius:99px;font-size:10.5px;font-weight:700;letter-spacing:.04em;vertical-align:1px}
.src.resmi{background:var(--ok-bg);color:var(--ok-fg)}
.src.media{background:var(--warn-bg);color:var(--warn-fg)}
.src.belum{background:var(--surface-2);color:var(--ink-3)}
.note{display:block;margin-top:4px;font-size:12.5px;color:var(--ink-3)}
.legend{display:flex;gap:14px;flex-wrap:wrap;font-size:13px;color:var(--ink-3);margin-bottom:16px}
@media (max-width:640px){ .spec tbody th{width:40%} .spec th,.spec td{padding:9px 8px;font-size:13.5px} }

/* ---- steps ---- */
.steps{list-style:none;margin:0;padding:0;display:grid;gap:14px;counter-reset:s}
.steps li{counter-increment:s;background:var(--surface);border:1px solid var(--line);border-radius:18px;padding:18px 20px 18px 58px;position:relative}
.steps li::before{content:counter(s);position:absolute;left:18px;top:18px;width:26px;height:26px;border-radius:99px;background:var(--navy);color:#fff;display:grid;place-items:center;font-size:13px;font-weight:700}
.steps h3{margin:0 0 4px;font-size:16.5px}
.steps p{margin:0;color:var(--ink-2);font-size:14.5px}

/* ---- footer ---- */
footer{background:var(--foot-bg);color:var(--foot-fg);margin-top:8px}
footer .wrap{padding-block:34px;display:grid;gap:26px;grid-template-columns:minmax(220px,1.4fr) repeat(2,minmax(150px,1fr))}
footer h2{font-size:17px;margin:0 0 4px;color:var(--foot-fg)}
footer p{margin:0;color:var(--foot-2);font-size:14px;max-width:38ch}
footer small{display:block;font-size:11px;letter-spacing:.16em;font-weight:600;color:var(--mint-2);margin-bottom:10px}
footer a{color:var(--foot-fg);text-decoration:none;font-size:14.5px}
footer a:hover{text-decoration:underline}
footer ul{list-style:none;margin:0;padding:0;display:grid;gap:8px}
.foot-base{border-top:1px solid rgba(255,255,255,.12)}
.foot-base .wrap{padding-block:16px;display:flex;justify-content:space-between;gap:12px;flex-wrap:wrap;font-size:12.5px;color:var(--foot-2)}
/* The privacy line (site.js) takes a row of its own under the note and the copyright. */
.foot-privacy{flex-basis:100%}
@media (max-width:700px){ footer .wrap{grid-template-columns:1fr} }

.mt{margin-top:22px}
.prose + .prose{margin-top:10px}
#loadErr{margin:24px auto;max-width:520px}

/* Event poster: the card shows a slice, the button opens the whole picture. */
.event .shot-btn{display:block;position:relative;width:100%;padding:0;border:0;background:none;cursor:zoom-in}
.event .shot-btn .zoom{position:absolute;right:10px;bottom:10px;padding:5px 10px;border-radius:99px;
  background:rgba(10,31,46,.74);color:#fff;font-size:12px;font-weight:600;letter-spacing:.01em;
  pointer-events:none}
.event .shot-btn:hover .zoom{background:rgba(10,31,46,.92)}
.event .more > summary{cursor:pointer;font-size:13.5px;font-weight:600;color:var(--accent);
  list-style:none;padding:4px 0}
.event .more > summary::-webkit-details-marker{display:none}
.event .more > summary::after{content:" +";font-weight:700}
.event .more[open] > summary::after{content:" \2212"}

dialog.lightbox{border:0;padding:24px;overflow:hidden;background:transparent;width:100%;height:100%;
  max-width:100vw;max-height:100vh;display:none;align-items:center;justify-content:center}
dialog.lightbox[open]{display:flex}
dialog.lightbox::backdrop{background:rgba(4,14,22,.88)}
dialog.lightbox img{max-width:min(94vw,860px);max-height:84vh;width:auto;height:auto;
  border-radius:14px;box-shadow:0 24px 60px rgba(0,0,0,.5)}
dialog.lightbox .lb-close{position:fixed;top:14px;right:16px;width:44px;height:44px;border-radius:50%;
  border:1px solid rgba(255,255,255,.28);background:rgba(0,0,0,.45);color:#fff;font-size:22px;
  line-height:1;cursor:pointer}
dialog.lightbox .lb-close:hover{background:rgba(0,0,0,.72)}
dialog.lightbox .lb-nav{position:fixed;top:50%;width:48px;height:48px;margin-top:-24px;padding:0;
  border-radius:50%;border:1px solid rgba(255,255,255,.28);background:rgba(0,0,0,.45);color:#fff;
  display:none;align-items:center;justify-content:center;cursor:pointer}
dialog.lightbox.multi .lb-nav{display:flex}
dialog.lightbox .lb-nav:hover{background:rgba(0,0,0,.72)}
dialog.lightbox .lb-nav.prev{left:16px}
dialog.lightbox .lb-nav.next{right:16px}
dialog.lightbox .lb-nav.next svg{transform:scaleX(-1)}
dialog.lightbox button:focus-visible{outline:2px solid #fff;outline-offset:2px}
dialog.lightbox .lb-bar{position:fixed;left:0;right:0;bottom:14px;padding:0 72px;text-align:center;
  color:#fff;font-size:13px;line-height:1.4;pointer-events:none;text-shadow:0 1px 6px rgba(0,0,0,.7)}
dialog.lightbox .lb-count{font-weight:600;letter-spacing:.04em}
dialog.lightbox .lb-count:not(:empty) + .lb-cap:not(:empty){margin-left:10px}
@media (max-width:600px){
  dialog.lightbox img{max-height:78vh}
  dialog.lightbox .lb-nav{top:auto;bottom:8px;margin-top:0;width:44px;height:44px}
  dialog.lightbox .lb-nav.prev{left:12px}
  dialog.lightbox .lb-nav.next{right:12px}
  dialog.lightbox .lb-bar{bottom:20px;padding:0 68px}
}


/* Both kinds of event share one list on the landing page, so each card says
   which it is. Dimming alone was ambiguous -- it could as easily read as
   disabled. */
.event .when .st{padding:2px 8px;border-radius:99px;font-size:10px;letter-spacing:.09em;
  font-weight:700;background:var(--ok-bg);color:var(--ok-fg)}
.event.past .when .st,.event .when .st.done{background:var(--surface-2);color:var(--ink-2)}

/* ---- tips & trik ---- */
/* Cards: cover 16:9, then badge, title and summary. The whole card is the link. */
.tip-grid{display:grid;gap:16px;grid-template-columns:repeat(auto-fill,minmax(min(100%,280px),1fr))}
.tip-card{display:flex;flex-direction:column;background:var(--surface);border:1px solid var(--line);border-radius:18px;
  overflow:hidden;color:inherit;text-decoration:none;transition:border-color .2s ease,box-shadow .2s ease,transform .2s ease}
.tip-card:hover,.tip-card:focus-visible{border-color:var(--line-2);box-shadow:0 10px 26px rgba(0,0,0,.14);transform:translateY(-2px)}
.tip-cover{aspect-ratio:16/9;background:var(--surface-2);overflow:hidden}
.tip-cover img{display:block;width:100%;height:100%;object-fit:cover}
/* No cover: the brand wash the hero uses, so the card still has a top. */
.tip-cover.none{background-image:linear-gradient(105deg,rgba(var(--tint-2),.34) 0%,rgba(var(--tint-1),.20) 55%,rgba(var(--tint-3),.12) 100%)}
.tip-body{padding:16px 18px 18px;display:flex;flex-direction:column;gap:8px;min-width:0}
.tip-body h3{margin:0;font-size:17px;line-height:1.3;letter-spacing:-.01em;overflow-wrap:anywhere}
.tip-body p{margin:0;color:var(--ink-2);font-size:14.5px;overflow-wrap:anywhere;
  display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
.tip-badge{align-self:flex-start;display:inline-flex;align-items:center;gap:5px;padding:2px 9px;border-radius:99px;
  font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase}
.tip-badge.free{background:var(--ok-bg);color:var(--ok-fg)}
.tip-badge.lock{background:var(--surface-2);color:var(--ink-2)}
/* Placeholder cards while the list is on its way: same box as a card, so nothing moves when the real ones land. */
.tip-card.skel{pointer-events:none}
.tip-card.skel .tip-cover,.tip-card.skel i{background:var(--surface-2)}
.tip-card.skel i{display:block;height:12px;border-radius:6px}
.tip-card.skel i.t{height:18px;width:72%}
.tip-card.skel i.s{width:92%}
.tip-card.skel i.s2{width:58%}
@media (prefers-reduced-motion:no-preference){ .tip-card.skel .tip-cover,.tip-card.skel i{animation:skel 1.4s ease-in-out infinite} }
@keyframes skel{50%{opacity:.55}}
/* Skeletons ("scaffolding"): grey shapes in the size of what is coming, in the page HTML so they show on the first
   paint. Anything marked data-sk is a placeholder: site.js replaces it with the real content, or removes it when the
   content cannot be loaded. Lines are <i class="sk">, boxes take the shape of their card. */
.sk{display:block;height:12px;border-radius:6px;background:var(--line)}
.sk.t{height:20px;width:70%}
.sk.h2{height:26px;width:280px;max-width:60vw;border-radius:8px}   /* fixed width: the empty h2 around it has none */
.sk.s{width:92%} .sk.s2{width:64%} .sk.s3{width:80%}
.sk-lines{display:grid;gap:10px}
.sk-card{background:var(--surface);border:1px solid var(--line);border-radius:18px;overflow:hidden;pointer-events:none}
.sk-card .sk-img{aspect-ratio:16/9;background:var(--surface-2)}
.sk-card .sk-body{padding:16px 18px 18px;display:grid;gap:10px}
.sk-tile{aspect-ratio:4/3;border-radius:14px;background:var(--surface-2)}
.sk-row td .sk{max-width:220px}
@media (prefers-reduced-motion:no-preference){
  .sk,.sk-card .sk-img,.sk-tile{animation:skel 1.4s ease-in-out infinite}
  /* Pictures still on their way: the frame pulses until the image is in (site.js adds .ld on load or error). */
  .tip-cover:not(.none):not(.ld),img.shot:not(.ld),.gal .shot-btn:not(.ld),.tip-hero-img:not(.ld){animation:skel-bg 1.4s ease-in-out infinite}
}
@keyframes skel-bg{50%{background-color:var(--line-2)}}
/* The Beranda hero holds its own placeholder lines: the reveal keeps the hero's real children hidden until the words
   arrive, so these stand outside that rule and go when site.js paints. */
.anim .hero .wrap > .sk-hero,.sk-hero{opacity:1;animation:none;display:grid;gap:14px}
.sk-hero .sk.k{width:120px;height:22px;border-radius:99px}
.sk-hero .sk.t{height:clamp(34px,5vw,56px);width:min(620px,90%);border-radius:12px}
.sk-hero .sk.t2{height:clamp(34px,5vw,56px);width:min(420px,65%);border-radius:12px}
.sk-hero .sk.l{height:16px;width:min(520px,85%)}
html.tip-open #tipView[hidden]{display:block}
/* /tips?id=… opened directly: /boot.js sets this before the first paint so the list's hero does not flash before the
   article; site.js drops it once it has taken over. */
html.tip-open #tipsHero,html.tip-open #tipsList{display:none}

/* Detail */
#tipView{border-top:0}
/* The article uses the site's full width like every other page; only running text
   keeps the 70ch measure the other pages' prose has, and players stay a sane size. */
.tip-detail{max-width:none}
.tip-back{display:inline-flex;align-items:center;gap:6px;min-height:44px;font-size:14px;font-weight:600;color:var(--accent);text-decoration:none}
.tip-back::before{content:"\2190"}
.tip-back:hover{text-decoration:underline;text-underline-offset:3px}
.tip-hero-img{margin:6px 0 22px;border-radius:18px;overflow:hidden;aspect-ratio:16/9;max-height:520px;width:100%;background:var(--surface-2)}
.tip-hero-img img{display:block;width:100%;height:100%;object-fit:cover}
.tip-title{font-size:clamp(28px,4.4vw,42px);line-height:1.1;margin:8px 0 12px;overflow-wrap:anywhere}
.tip-summary{margin:0 0 26px;max-width:70ch;white-space:normal;overflow-wrap:anywhere}
.tip-loading{color:var(--ink-3)}
.tip-err{display:flex;flex-direction:column;align-items:flex-start;gap:12px;color:var(--ink-2)}
.tip-err p{margin:0}
.tip-missing .lede{margin:0 0 22px}

/* The blocks. Rhythm comes from one gap between blocks rather than from each
   block's own margins, so any order of blocks spaces the same. */
.tip-blocks{display:flex;flex-direction:column;gap:22px}
.tip-blocks > .prose{max-width:70ch}
.tip-blocks .prose p:last-child{margin-bottom:0}
.tip-blocks .tb-h{margin:12px 0 -8px;font-size:clamp(20px,2.6vw,25px);overflow-wrap:anywhere}
.tip-blocks figure{margin:0}
.tip-blocks figcaption{margin-top:8px;font-size:13.5px;color:var(--ink-3);text-align:center}
/* Fits the picture: a landscape one runs the full width, an upright one stops at the screen height. */
.tb-img{width:fit-content;max-width:100%}
.tb-img .shot-btn{display:block;width:100%;padding:0;border:1px solid var(--line);border-radius:14px;overflow:hidden;
  background:var(--surface-2);cursor:zoom-in}
.tb-img img{display:block;width:auto;max-width:100%;max-height:80vh;height:auto}
.tb-frame{position:relative;border-radius:14px;overflow:hidden;background:#000}
.tb-frame iframe{display:block;width:100%;height:100%;border:0}
.tb-video{max-width:880px}
.tb-video .tb-frame{aspect-ratio:16/9}
/* TikTok and YouTube Shorts are upright; full width on a desktop would be taller than the screen. */
.tb-video.tb-tall{max-width:340px}
.tb-video.tb-tall .tb-frame{aspect-ratio:9/16}
/* Instagram's embed is a card (header, picture, likes), not a bare player, and it
   reports its own height (blocks.js); this is the size it measured at 400 px wide
   until that message arrives. */
.tb-video.tb-instagram{max-width:400px}
.tb-instagram .tb-frame{aspect-ratio:auto;background:#fff;border:1px solid var(--line)}
.tb-instagram .tb-frame iframe{height:690px}
.tb-video figcaption{text-align:center}
.tb-watch{display:inline-flex;align-items:center;min-height:34px;font-weight:600;color:var(--accent);text-underline-offset:3px}
.tb-video-msg{padding:16px;border:1px dashed var(--line-2);border-radius:14px;color:var(--ink-3);font-size:14px;text-align:center}
.tb-note{max-width:70ch;border-radius:14px;padding:14px 16px;border:1px solid transparent;display:flex;flex-direction:column;gap:4px}
.tb-note .prose{color:inherit;max-width:none}
.tb-note .prose strong{color:inherit}
.tb-note.info{background:var(--ok-bg);color:var(--ink);border-color:color-mix(in srgb,var(--ok-fg) 25%,transparent)}
.tb-note.warn{background:var(--warn-bg);color:var(--ink);border-color:color-mix(in srgb,var(--warn-fg) 30%,transparent)}
.tb-note-label{font-size:11.5px;letter-spacing:.08em;text-transform:uppercase}
.tb-note.info .tb-note-label{color:var(--ok-fg)}
.tb-note.warn .tb-note-label{color:var(--warn-fg)}

/* Locked: the summary stays, the body is replaced by this. */
.tip-lock{max-width:720px;background:var(--surface);border:1px solid var(--line);border-top:3px solid var(--teal);border-radius:18px;
  padding:22px;display:flex;flex-direction:column;gap:14px}
.tip-lock-head{display:flex;align-items:center;gap:8px;font-size:17px;color:var(--ink)}
.tip-lock-head svg{width:18px;height:18px;color:var(--accent)}
.tip-lock p{margin:0;color:var(--ink-2)}
.tip-lock .tip-lock-help{font-size:14px;color:var(--ink-3)}
.tip-lock-help a{color:var(--accent);text-underline-offset:3px}
.sk-article{display:grid;gap:14px}
.sk-article .sk-img{aspect-ratio:16/9;max-height:520px;width:100%;border-radius:18px;margin:6px 0 8px;background:var(--surface-2)}   /* as .tip-hero-img */
.sk-article .sk.t{height:34px;width:80%;border-radius:10px}
@media (prefers-reduced-motion:no-preference){ .sk-article .sk-img{animation:skel 1.4s ease-in-out infinite} }
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
.tip-loading{margin-top:8px}
