/* Reusable page chrome + component classes, styled per the UPUBLIC
   design system tokens in application.css. */

.container{max-width:var(--container-max);margin:0 auto;padding:0 8vw}
@media (max-width:700px){.container{padding:0 6vw}}

/* Header — matches hub-live.html's header exactly: logo, My inHUB
   (outlined navbtn), Basket (solid navbtn with a count badge), and a
   "back to UPUBLIC" link on the far right. */
.site-header{padding:16px 8vw;border-bottom:1px solid var(--border-default);background:var(--surface-card);display:flex;justify-content:space-between;align-items:center;gap:24px;position:sticky;top:0;z-index:40;flex-wrap:wrap}
.site-header .logo{height:48px;width:auto;cursor:pointer}
.site-header nav{display:flex;align-items:center;gap:10px}
.site-header .back{font-size:13px;color:var(--text-muted);text-decoration:none}
.site-header .back:hover{color:var(--link-hover)}

.navbtn{font-family:var(--font-family);font-size:13px;font-weight:var(--weight-medium);padding:9px 16px;border-radius:var(--radius-md);border:1px solid var(--border-default);background:var(--surface-card);color:var(--text-primary);cursor:pointer;display:inline-flex;align-items:center;gap:8px;text-decoration:none;transition:border-color var(--duration-base) ease,background var(--duration-base) ease}
.navbtn:hover{border-color:var(--brand-hub);background:rgba(121,191,15,.07);text-decoration:none}
.navbtn.solid{background:var(--brand-hub);border-color:var(--brand-hub);color:var(--white)}
.navbtn.solid:hover{background:var(--brand-hub-hover);border-color:var(--brand-hub-hover)}
.navbtn .count{min-width:19px;height:19px;padding:0 5px;border-radius:var(--radius-pill);background:var(--brand-hub);color:var(--white);font-size:11px;font-weight:var(--weight-semibold);display:none;align-items:center;justify-content:center}
.navbtn.solid .count{background:var(--white);color:var(--brand-hub-hover)}
.navbtn .count.on{display:inline-flex}

/* Flash messages */
.flash{padding:12px 16px;border-radius:var(--radius-sm);margin:16px 8vw 0;font-size:14px}
.flash.notice{background:rgba(121,191,15,.12);color:var(--brand-hub-hover);border:1px solid rgba(121,191,15,.35)}
.flash.alert{background:rgba(178,58,46,.08);color:var(--danger);border:1px solid rgba(178,58,46,.3)}

/* Buttons */
.btn{font-family:var(--font-family);font-size:14px;line-height:normal;font-weight:var(--weight-medium);padding:11px 20px;border-radius:var(--radius-md);border:1px solid var(--brand-hub);background:var(--brand-hub);color:var(--white);cursor:pointer;display:inline-block;text-align:center;transition:background var(--duration-base) ease,box-shadow var(--duration-base) ease}
.btn:hover{background:var(--brand-hub-hover);border-color:var(--brand-hub-hover);box-shadow:var(--shadow-sm);color:var(--white);text-decoration:none}
.btn.ghost{background:transparent;color:var(--brand-hub-hover);border-color:var(--border-default)}
.btn.ghost:hover{background:rgba(121,191,15,.08);border-color:var(--brand-hub)}
.btn.small{padding:7px 14px;font-size:12.5px}
.btn.wide{display:block;width:100%;box-sizing:border-box}
summary.btn{list-style:none}
summary.btn::-webkit-details-marker{display:none}

/* Generic card (basket lines, forms, My inHUB, etc.) — has its own
   padding, unlike .product-card below which needs an edge-to-edge
   image at the top. */
.card{background:var(--surface-card);border:1px solid var(--border-default);border-radius:var(--radius-lg);box-shadow:var(--shadow-sm);padding:var(--space-5);margin-bottom:var(--space-4)}

.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:var(--space-5)}

/* Session cards (course unit-selector page) — same .product-card
   pattern as the catalogue, just smaller: more, denser cards rather
   than a few large ones dominating the page. */
.sessions-grid{grid-template-columns:repeat(auto-fill,minmax(190px,1fr));gap:var(--space-4)}
.sessions-grid .shot{aspect-ratio:16/9}
.sessions-grid .card-body{padding:14px;gap:10px}
.sessions-grid h3{font-size:14px;margin-bottom:8px}

/* Product card — the image well was originally 16:9 (matching
   hub-live.html exactly), but the user found the catalogue cards too
   tall and specifically wanted shorter, not narrower — so the image
   area (over half the card's height) is now a flatter 2:1, scoped so
   it doesn't touch the session cards above (.sessions-grid .shot keeps
   the original 16:9). Then a body holding the category icon + access
   badge, title, and description. */
.product-card{background:var(--surface-card);border:1px solid var(--border-default);border-radius:var(--radius-lg);box-shadow:var(--shadow-sm);overflow:hidden;display:flex;flex-direction:column;transition:transform var(--duration-base) ease,box-shadow var(--duration-base) ease,border-color var(--duration-base) ease}
.product-card:hover{transform:translateY(-4px);box-shadow:var(--shadow-md);border-color:var(--brand-hub)}
.shot{aspect-ratio:2/1;background:var(--surface-canvas);border-bottom:1px solid var(--border-default);display:flex;align-items:center;justify-content:center;position:relative;overflow:hidden}
.shot img{width:100%;height:100%;object-fit:contain;padding:22px;background:var(--white)}
.shot img.fill{object-fit:cover;padding:0}
.shot video{width:100%;height:100%;object-fit:cover;display:block}
.shot .cap{position:absolute;left:0;right:0;bottom:0;background:rgba(255,255,255,.92);border-top:1px solid var(--border-default);padding:8px 14px;font-size:13px;font-weight:var(--weight-semibold);letter-spacing:.1em;text-transform:uppercase;color:var(--text-primary);text-align:center}
.shot .ph{font-size:11px;font-weight:var(--weight-medium);color:var(--text-muted);text-align:center;line-height:1.7;letter-spacing:.02em;border:1px dashed var(--border-default);border-radius:var(--radius-sm);padding:12px 16px}
.shot .ph b{display:block;font-weight:var(--weight-semibold);color:var(--brand-hub-hover);font-size:12px}
.product-card .card-body{padding:14px;display:flex;flex-direction:column;gap:8px;flex:1}
.icon-row{display:flex;align-items:center;gap:12px}
.icon-slot{width:44px;height:44px;flex-shrink:0;border:1px solid var(--border-default);border-radius:var(--radius-sm);overflow:hidden;background:var(--surface-canvas)}
.icon-slot img{display:block;width:100%;height:100%}
.product-card h3{font-size:16px;font-weight:var(--weight-semibold);letter-spacing:-0.005em;margin:0}
.product-card .desc{font-size:13px;font-weight:var(--weight-light);color:var(--text-muted);flex:1}

/* Same image-well pattern, sized for the product-info modal. */
.mshot{aspect-ratio:16/9;background:var(--surface-canvas);border:1px solid var(--border-default);border-radius:var(--radius-md);display:flex;align-items:center;justify-content:center;overflow:hidden;margin-bottom:18px;position:relative}
.mshot img{width:100%;height:100%;object-fit:contain;padding:26px;background:var(--white)}
.mshot img.fill{object-fit:cover;padding:0}
.mshot video{width:100%;height:100%;object-fit:cover;display:block}
.mshot .cap{position:absolute;left:0;right:0;bottom:0;background:rgba(255,255,255,.92);border-top:1px solid var(--border-default);padding:10px 18px;font-size:16px;font-weight:var(--weight-semibold);letter-spacing:.1em;text-transform:uppercase;color:var(--text-primary);text-align:center}

/* Badges */
.badge{display:inline-block;font-size:10px;font-weight:var(--weight-semibold);letter-spacing:.06em;text-transform:uppercase;padding:4px 10px;border-radius:var(--radius-pill)}
.badge.paid{background:rgba(121,191,15,.16);color:var(--brand-hub-hover)}
.badge.free{background:rgba(61,110,143,.14);color:#2F5A76}
.badge.restricted{background:rgba(191,161,59,.18);color:#8A6E1F}
.badge.owned{background:rgba(38,114,24,.12);color:var(--brand-upublic)}

.price{font-size:16px;font-weight:var(--weight-semibold)}
.price .was{font-size:12.5px;font-weight:var(--weight-regular);color:var(--text-muted);text-decoration:line-through;margin-left:6px}

/* Forms */
.field{margin-bottom:var(--space-4)}
.field label{display:block;font-size:13px;font-weight:var(--weight-medium);margin-bottom:6px}
.field .hint{font-weight:var(--weight-regular);color:var(--text-muted)}
.field input[type=text],.field input[type=email],.field input[type=password],.field input[type=date],.field input[type=number],.field textarea,.field select{width:100%;font-family:var(--font-family);font-size:14px;padding:11px 14px;border:1px solid var(--border-default);border-radius:var(--radius-sm);background:var(--surface-canvas);color:var(--text-primary);outline:none}
.field input:focus,.field textarea:focus,.field select:focus{border-color:var(--focus-ring);box-shadow:0 0 0 3px rgba(61,110,143,.12)}
.check-row{display:flex;gap:10px;align-items:flex-start;font-size:13px;color:var(--text-muted);margin-bottom:var(--space-4)}
.check-row input{width:16px;height:16px;margin-top:2px;accent-color:var(--brand-hub);flex-shrink:0}
.chip-row{display:flex;flex-wrap:wrap;gap:8px}
.chip-row label{font-size:12.5px;font-weight:var(--weight-medium);padding:8px 14px;border-radius:var(--radius-pill);border:1px solid var(--border-default);background:var(--surface-canvas);display:inline-flex;align-items:center;gap:6px;cursor:pointer}

/* Simple table (admin grants) */
table.plain{width:100%;border-collapse:collapse;background:var(--surface-card);border:1px solid var(--border-default);border-radius:var(--radius-lg);overflow:hidden;box-shadow:var(--shadow-sm)}
table.plain th{text-align:left;font-size:11px;font-weight:var(--weight-medium);text-transform:uppercase;letter-spacing:.06em;color:var(--text-muted);padding:12px 16px;border-bottom:1px solid var(--border-default)}
table.plain td{font-size:13.5px;padding:12px 16px;border-top:1px solid var(--border-default);vertical-align:top}
table.plain tbody tr:first-child td{border-top:none}

/* Full-bleed hero band (catalogue page). Rendered outside <main
   class="container"> via content_for(:above_main), same reasoning as
   .site-header — a section that needs an edge-to-edge white background,
   distinct from the gray canvas everything else sits on, can't get that
   from inside the max-width container without breakout hacks. Matches
   hub-live.html's own .hero exactly: self-contained 8vw padding, no
   wrapping container needed. */
.hero{padding:40px 8vw 56px;background:var(--surface-card)}

/* Courses/Resources section headers — icon + title + live count, plus
   a one-line subtitle. Matches hub-live.html's .cat-head/.cat-sub. */
.cat-head{display:flex;align-items:center;gap:14px;margin-bottom:6px}
.cat-head .ic{width:38px;height:38px;border:1px solid var(--border-default);border-radius:var(--radius-sm);overflow:hidden;flex-shrink:0}
.cat-head .ic img{display:block;width:100%;height:100%}
.cat-head h2{margin:0;font-size:clamp(24px,3vw,34px);font-weight:var(--weight-semibold);letter-spacing:-0.01em}
.cat-head .n{font-size:12px;font-weight:var(--weight-medium);color:var(--text-muted)}
.cat-sub{font-size:16px;font-weight:var(--weight-light);color:var(--text-muted);max-width:560px;margin-bottom:26px}

/* Horizontal rail between Courses/Resources — the Hub-page counterpart
   to the landing page's vertical divider (.landing .divider/.dot),
   same idea (a traveling brand-colour dot) on one axis instead of two,
   since this page only has one brand to represent. Matches
   hub-live.html's .railwrap/.rail/.rail-dot exactly, including the 26s
   pace — deliberately NOT the same speed as the landing page's dots
   (12s), which travel a much shorter distance between two fixed points. */
.railwrap{margin:56px 0;position:relative}
.rail{height:1px;width:100%;background:var(--border-default)}
.rail-dot{position:absolute;top:50%;left:0;width:9px;height:9px;margin-top:-4.5px;margin-left:-4.5px;border-radius:50%;background:var(--brand-hub);animation:ride 26s linear infinite}
@keyframes ride{0%{left:0}100%{left:100%}}
@media (prefers-reduced-motion:reduce){.rail-dot{animation:none;left:50%}}

/* Pre-launch "get updates" form at the foot of the Hub page. Matches
   hub-live.html's .intro/.signoff/.form-card exactly. */
/* Full-bleed white band for the pre-launch section — same "sits outside
   <main class="container">" full-bleed treatment as .hero/footer,
   rendered via content_for(:below_main) since it's at the end of the
   page rather than the start. Distinguishes it from the gray canvas
   Courses/Resources sit on above it. Left-aligned, not centered — same
   convention as every other section on this page. */
.launch-band{background:var(--surface-card);text-align:left}
.intro{padding:8px 8vw 0;text-align:left}
.intro p{font-size:16px;font-weight:var(--weight-light);color:var(--text-muted);max-width:620px;margin-bottom:14px}
.signoff{padding:0 8vw;font-size:14px;color:var(--text-muted);max-width:560px;text-align:left}
.form-section{padding:32px 8vw 88px;text-align:left}
.form-card{max-width:520px;background:var(--surface-card);border:1px solid var(--border-default);border-radius:var(--radius-lg);padding:36px;box-shadow:var(--shadow-sm)}
.form-card h2{font-size:18px;font-weight:var(--weight-semibold);margin-bottom:6px}
.form-card .sub{font-size:13px;color:var(--text-muted);margin-bottom:24px}
.fine-print{font-size:12px;color:var(--text-muted);margin-top:14px}

/* Site-wide footer — full-bleed like .site-header, no background of its
   own (sits on the gray canvas), rendered on every page. */
footer{padding:40px 8vw;border-top:1px solid var(--border-default)}
footer p{color:var(--text-muted);margin-bottom:6px}
footer a{color:var(--text-muted)}

.eyebrow{font-size:var(--text-eyebrow);font-weight:var(--text-eyebrow-weight);letter-spacing:var(--text-eyebrow-tracking);text-transform:var(--text-eyebrow-transform);color:var(--text-muted)}
.muted{color:var(--text-muted)}
.section{padding:var(--space-7) 0}

/* My inHUB reuses the catalogue's .product-card shell for the small
   image + description the user asked for, but scoped tighter here:
   less vertical breathing room between Courses/Resources than the
   public catalogue wants (that page's sections stay at --space-7), and
   a shorter image well + narrower grid so the cards read as small,
   matching what was already there before images were added. */
.my-inhub .section{padding:var(--space-4) 0}
/* Scoped to .owned-items specifically, not every .grid in .my-inhub —
   "Also in the Hub" below reuses the catalogue's full-size .product-card
   via the shared _product_card partial and must keep its own sizing. */
.owned-items .grid{grid-template-columns:repeat(auto-fill,minmax(200px,1fr))}
.owned-items .owned-card .shot{aspect-ratio:3/1}
.owned-items .owned-card .card-body{padding:12px}

/* Catalogue's "Here's what to expect"/Courses/Resources sections,
   tightened by a third (48px → 32px) per feedback — the launch-band's
   own padding-top (64px → 43px, same ratio) is reduced right alongside
   it in the view template so the gap after Resources shrinks too. */
.catalogue-sections .section{padding:var(--space-6) 0}
.mark-dot{display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--brand-hub);margin-right:8px}

/* Split UPUBLIC/Hub landing page. A static layout — the design's
   click-triggered rotating-line transition into the Hub needs JS state
   this app doesn't have yet, so "Enter the Hub" is a plain navigation
   for now. The passive idle-dot drift is pure CSS, so that's kept. */
.landing{position:relative;display:grid;grid-template-columns:1fr 1fr;height:100vh}
.landing .panel{position:relative;height:100vh;padding:0 8vw;display:flex;flex-direction:column;justify-content:center}
.landing a.panel{cursor:pointer;text-decoration:none;color:inherit;transition:background var(--duration-base) ease}
.landing a.panel:hover{background:rgba(38,114,24,0.05)}
.landing .eyebrow-sub{font-size:16px;font-weight:var(--weight-regular);color:var(--brand-upublic);margin-top:10px;margin-bottom:16px;text-transform:uppercase;letter-spacing:.04em}
.landing .body-text{font-size:14px;font-weight:var(--weight-light);line-height:1.65;color:var(--text-muted);max-width:340px}
.landing .logo-mark{height:clamp(44px,7vw,68px);width:auto;margin-bottom:14px;margin-left:-4px;align-self:flex-start;flex-shrink:0}
.landing .hub-logo-mark{height:clamp(44px,7vw,68px);width:auto;margin-bottom:14px;align-self:flex-end;flex-shrink:0}
.landing .hub-panel{align-items:flex-end;text-align:right;justify-content:flex-start}
/* The two panels aren't meant to sit at the same height — UPUBLIC's
   content is vertically centered (nudged up slightly by top-third's own
   margin), while the Hub's content is deliberately staggered lower,
   starting at the vertical halfway point of the screen (never above it,
   and never overlapping UPUBLIC's own content if that runs unusually
   tall) — computed in landing_controller.js's position(), since it
   depends on real rendered heights, not a fixed CSS value. */
.landing .content-block{display:flex;flex-direction:column}
.landing .content-block.top-third{margin-bottom:9vh}
.landing .hub-panel .content-block.bottom-third{position:absolute;left:8vw;right:8vw}
/* Both CTAs are hidden until the cursor is over their own half of the
   split page — matches flow.html's .contact/.hub-trigger exactly
   (opacity:0 + a small inward slide, revealed on .panel:hover). Missed
   when this page was first built; ported now. */
.landing .contact{margin-top:24px;font-size:13px;font-weight:var(--weight-light);color:var(--text-primary);display:flex;align-items:center;gap:6px;opacity:0;transform:translateX(-4px);transition:opacity 200ms ease,transform 200ms ease,color 200ms ease}
.landing .panel:hover .contact{opacity:1;transform:translateX(0)}
.landing .contact:hover{color:var(--accent-cool)}
/* Verbatim from flow.html's own .hub-trigger rule. */
.landing .hub-trigger{margin-top:24px;font-size:13px;font-weight:var(--weight-medium);font-family:var(--font-family);background:var(--surface-card);border:1px solid var(--border-default);padding:10px 20px;border-radius:var(--radius-md);cursor:pointer;color:var(--text-primary);align-self:flex-end;box-shadow:var(--shadow-sm);opacity:0;transform:translateX(4px);transition:border-color 200ms ease,color 200ms ease,box-shadow 200ms ease,opacity 200ms ease,transform 200ms ease}
.landing .panel:hover .hub-trigger{opacity:1;transform:translateX(0)}
.landing .hub-trigger:hover{border-color:var(--brand-hub-hover);color:var(--brand-hub-hover);box-shadow:var(--shadow-md)}
.landing .hub-trigger:hover{color:var(--brand-hub-hover);box-shadow:0 6px 20px rgba(35,40,42,.12)}
.landing .divider{position:absolute;top:0;left:50%;width:1px;height:100%;background:var(--border-default)}
.landing .dot{position:absolute;left:50%;width:9px;height:9px;border-radius:50%;transform:translateX(-50%)}
.landing .dot.left{background:var(--brand-upublic);top:15vh;animation:meet-upper 12s ease-in-out infinite}
.landing .dot.right{background:var(--brand-hub);top:85vh;animation:meet-lower 12s ease-in-out infinite;animation-delay:2s}
@keyframes meet-upper{0%,100%{transform:translate(-50%,0)}50%{transform:translate(-50%,32vh)}}
@keyframes meet-lower{0%,100%{transform:translate(-50%,0)}50%{transform:translate(-50%,-32vh)}}
@media (prefers-reduced-motion:reduce){.landing .dot{animation:none}}
.landing .legal-footer{position:absolute;bottom:12px;left:8vw;font-size:9px;color:var(--text-muted);line-height:1.5}
.landing .legal-footer a{color:var(--text-muted)}

/* Enter-the-Hub transition — see landing_controller.js. The rotating
   line takes over from the real .divider at an identical visual spot
   (no fade), then two dots (Web Animations API, driven independently
   of the line so their easing can be matched exactly rather than
   inherited) sweep outward along it — green to the far right, blue to
   the far left — then the green dot sweeps back left as the real
   navigation to the Hub fires. */
.rotator-line{position:fixed;top:50%;left:50%;width:100vmax;height:1px;background:var(--border-default);transform-origin:center center;transform:translate(-50%,-50%) rotate(90deg);z-index:30;pointer-events:none;opacity:1;transition:opacity 300ms ease}
.rotator-line.active{transition:transform 1600ms cubic-bezier(.6,0,.3,1),opacity 300ms ease;transform:translate(-50%,-50%) rotate(0deg)}
.rotator-line.fading{opacity:0}
.transition-dot{position:fixed;top:50%;left:50%;width:9px;height:9px;margin-top:-4.5px;margin-left:-4.5px;border-radius:50%;z-index:31;pointer-events:none}
.transition-dot.upublic{background:var(--brand-upublic)}
.transition-dot.hub{background:var(--brand-hub)}

/* Hub-page arrival half of the same transition — see
   hub_entrance_controller.js. Only ever applied when the landing page's
   sessionStorage flag says we just came from there; a no-op on every
   normal visit/reload/direct link. */
body.entering-hub{transform:translateX(100%)}
body.entering-hub.animate{transition:transform 1200ms cubic-bezier(.33,1,.68,1);transform:translateX(0)}
@media (prefers-reduced-motion:reduce){body.entering-hub,body.entering-hub.animate{transform:none;transition:none}}
@media (max-width:720px){
  .landing{grid-template-columns:1fr;height:auto}
  .landing .divider,.landing .dot{display:none}
  .landing .panel{height:auto;min-height:50vh;padding:60px 7vw}
  .landing .hub-panel{align-items:flex-start;text-align:left;justify-content:center}
  .landing .content-block{position:static;height:auto;padding:0}
}

/* Product-info modal + basket drawer. Values match hub-live.html
   exactly. Both overlay the current page rather than navigating away —
   see modal_controller.js / drawer_controller.js. */
.scrim{position:fixed;inset:0;background:rgba(35,40,42,.42);opacity:0;pointer-events:none;transition:opacity 220ms ease;z-index:50}
.scrim.on{opacity:1;pointer-events:auto}

.mhead{display:flex;justify-content:space-between;align-items:flex-start;gap:16px;padding:22px 26px 14px;border-bottom:1px solid var(--border-default)}
.mhead h2{font-size:17px;margin:0}
.x{background:none;border:none;font-size:20px;line-height:1;color:var(--text-muted);cursor:pointer;padding:2px 4px}
.x:hover{color:var(--text-primary)}
.mbody{padding:24px 26px 26px;overflow-y:auto}
.mbody h4{font-size:12px;font-weight:var(--weight-semibold);letter-spacing:.06em;text-transform:uppercase;color:var(--text-muted);margin:22px 0 10px}
.mbody ul{list-style:none;margin:0 0 16px;padding:0}
.mbody ul li{display:flex;gap:12px;align-items:flex-start;padding:12px 0;border-top:1px solid var(--border-default);font-size:14px;font-weight:var(--weight-light)}
.mbody ul li:last-child{border-bottom:1px solid var(--border-default)}

.modal{position:fixed;z-index:60;left:50%;top:50%;transform:translate(-50%,-48%) scale(.97);opacity:0;pointer-events:none;width:min(620px,92vw);max-height:88vh;overflow-y:auto;background:var(--surface-card);border-radius:var(--radius-lg);box-shadow:var(--shadow-md);transition:opacity 220ms ease,transform 260ms cubic-bezier(.34,1.3,.64,1)}
.modal.on{opacity:1;pointer-events:auto;transform:translate(-50%,-50%) scale(1)}

.drawer-panel{position:fixed;z-index:60;top:0;right:0;height:100%;width:min(420px,100vw);background:var(--surface-card);box-shadow:var(--shadow-md);transform:translateX(102%);transition:transform 300ms cubic-bezier(.34,1.1,.64,1);display:flex;flex-direction:column;overflow-y:auto}
.drawer-panel.on{transform:translateX(0)}
.drawer-panel .mbody{flex:1}
