/* ============================================================
   SHARED TOKENS
============================================================ */
:root {
  /* The warm swap, 2026-09-10. Brand green and amber are UNCHANGED — the identity was
     never the problem. What moved is the ground it sits on: a cool grey-green page with
     neutral ink became warm cream with warm ink, which is what made the mockups feel like
     a place rather than a form. Values only; nothing here can move a pixel. */
  --brand:#1a3a2a; --brand-mid:#2d6148; --brand-light:#3e7a5c; --brand-pale:#e4ede5;
  --accent:#f0a500; --accent-pale:#fdf3df;
  /* The accent when it has to be READ on a light ground. #f0a500 is 1.95:1 on cream — fine as
     a fill behind dark text, unreadable as text itself. This is the ochre the approved phone
     header uses for "rel": 3.83:1, still unmistakably the yellow family. It does not follow
     the Site Editor's accent setting; if an admin changes the accent, revisit this. */
  --accent-ink:#a87309;
  /* Terracotta. Defined now, spent later — links and secondary actions are still brand
     green, and repainting them is a change of its own rather than a side effect of this. */
  --accent-warm:#c2603c;
  --text:#241f1a; --text-muted:#6e655c; --text-faint:#746a62;
  /* --text-faint was #9a9188: 3.1:1 on white, 2.9 on cream, 2.7 on sand — under AA for the small
     meta text it carries in 60 rules. #746a62 is the lightest grey on the same warm line that
     reaches 4.5:1 on all three grounds (5.28 / 4.93 / 4.53). It sits barely lighter than
     --text-muted now; the hierarchy between the two is carried by size, which it already was. */
  --bg:#faf7f0; --bg-alt:#f3ede2; --surface:#fff; --border:#eae3d8;
  --radius:14px; --radius-sm:8px;
  --danger:#c0392b; --danger-pale:#fdf0ee;
  --warning:#d4860a; --warning-pale:#fff8e6;
  --success:#1a7a45; --success-pale:#e8f6ee;
  --pin:#6b21a8; --pin-pale:#f3e8ff;

  /* ---- CATEGORY PALETTE — THE SINGLE SOURCE OF TRUTH ----------------------
     Change a category colour HERE and nowhere else. Both sides read these:
       • CSS  — the .cat-* classes below (the post-category picker buttons)
       • JS   — CATEGORY_COLORS in js/config.js holds var(--cat-…) strings, which
                it injects straight into listing-card style attributes.
     They used to be two hardcoded copies of the same hexes, and they had already
     drifted: the Books picker button was painted with the TECHNOLOGY blue.

     Soft, tonal backgrounds with a deep same-hue text, all in one lightness band
     so the set reads as one family rather than a rainbow. */
  --cat-housing-bg:#E4EDE5;            --cat-housing-text:#2C5741;             /* sage          */
  --cat-clothing-bg:#F6E9EC;           --cat-clothing-text:#7A3B4A;            /* dusty rose    */
  --cat-technology-bg:#E3EAF2;         --cat-technology-text:#3F5871;          /* slate blue    */
  --cat-donation-bg:#F7EFE1;           --cat-donation-text:#7A5B2E;            /* warm sand     */
  --cat-organization_event-bg:#EDE7F3; --cat-organization_event-text:#4A3D6E;  /* muted lavender*/
  --cat-books-bg:#EEF1E2;              --cat-books-text:#4F5C28;               /* moss/olive    */
  --cat-other-bg:#EEEBE4;              --cat-other-text:#4A423B;               /* warm greige   */

  /* Book-TYPE colours (textbook vs other book). Same deal: js/books.js BOOK_COLORS and the
     .btype-* classes both read these, so there is one place to change them. They deliberately
     have their own variables rather than borrowing --cat-technology-*/--cat-donation-*, so
     recolouring a category can never silently recolour the book-type buttons. */
  --btype-course-bg:#E3EAF2;           --btype-course-text:#3F5871;            /* slate blue    */
  --btype-other-bg:#F7EFE1;            --btype-other-text:#7A5B2E;             /* warm sand     */

  /* ---- SCALES ------------------------------------------------------------
     Phase 0, 2026-09-10. Definitions only — nothing reads these yet, so adding
     them changes nothing on screen. Later phases spend them instead of inventing
     numbers, which is how 29 font sizes and 28 one-off shadows happened.

     Breakpoints deliberately are NOT here: a custom property cannot be used
     inside a @media query. The three the layout is settling on are
       <= 768px    phone    (the bottom tab bar switches on here)
       769-1100px  tablet
       >= 1101px   desktop
     — write those literally, and do not add a fourth. */

  /* Type — 7 steps, replacing sizes from 9px to 72px, odd halves included. */
  --fs-display:32px;   /* page titles, serif           */
  --fs-title:24px;     /* section heads, serif         */
  --fs-heading:19px;   /* card and feed heads, serif   */
  --fs-lead:16px;      /* listing titles, lead copy    */
  --fs-body:15px;      /* body — the current base      */
  --fs-small:13px;     /* meta lines, buttons          */
  --fs-micro:11px;     /* labels, badges, tab captions */

  /* Space — one rhythm for padding, margin and gap. */
  --sp-1:4px; --sp-2:8px; --sp-3:12px; --sp-4:16px;
  --sp-5:24px; --sp-6:32px; --sp-7:48px;

  /* Radius — --radius and --radius-sm above are unchanged and still correct. */
  --radius-lg:20px;
  --radius-pill:999px;

  /* Elevation — 4 steps. --shadow-md is the .listing-card:hover shadow already
     used further down; the other three are new. */
  --shadow-xs:0 1px 2px rgba(22,33,28,.06);
  --shadow-sm:0 2px 8px rgba(22,33,28,.07);
  --shadow-md:0 8px 24px rgba(26,58,42,.10);
  --shadow-lg:0 16px 48px rgba(22,33,28,.16);

  /* Motion */
  --dur-fast:.12s; --dur:.18s; --dur-slow:.28s;
  --ease:cubic-bezier(.2,.8,.2,1);

  /* Focus — defined now, applied in the accessibility pass. Today there are
     9 `outline:none` against only 6 :focus-visible rules, so some controls
     disappear when you tab to them. */
  --focus-ring:2px solid var(--brand-light);
  --focus-offset:2px;

  /* Hit target — the floor for anything tappable. */
  --tap-min:44px;
}
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:'DM Sans',sans-serif;background:var(--bg);color:var(--text);min-height:100vh;font-size:15px;}

/* ============================================================
   ROLE SELECTOR — landing screen
============================================================ */

/* ============================================================
   SHARED LOGIN MODAL
============================================================ */
.modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.55);z-index:500;align-items:center;justify-content:center;padding:20px;}
.modal-overlay.open{display:flex;}
.modal{background:var(--surface);border-radius:var(--radius);width:100%;max-width:480px;max-height:90vh;overflow-y:auto;}
.modal-header{padding:22px 24px 0;display:flex;align-items:center;justify-content:space-between;}
.modal-title{font-family:'DM Serif Display',serif;font-size:22px;}
.modal-close{background:var(--bg);border:none;width:32px;height:32px;border-radius:50%;cursor:pointer;font-size:18px;color:var(--text-muted);display:flex;align-items:center;justify-content:center;}
.modal-body{padding:20px 24px 24px;}
.form-group{margin-bottom:16px;}
.form-label{display:block;font-size:12px;font-weight:600;color:var(--text-muted);margin-bottom:6px;text-transform:uppercase;letter-spacing:.04em;}
.form-input{width:100%;padding:10px 14px;border:1px solid var(--border);border-radius:var(--radius-sm);font-size:14px;font-family:'DM Sans',sans-serif;background:var(--bg);color:var(--text);transition:border .15s;outline:none;}
.form-input:focus{border-color:var(--brand-light);background:#fff;}
.form-select{width:100%;padding:10px 14px;border:1px solid var(--border);border-radius:var(--radius-sm);font-size:14px;font-family:'DM Sans',sans-serif;background:var(--bg);color:var(--text);outline:none;cursor:pointer;}
.form-textarea{width:100%;padding:10px 14px;border:1px solid var(--border);border-radius:var(--radius-sm);font-size:14px;font-family:'DM Sans',sans-serif;background:var(--bg);color:var(--text);resize:vertical;min-height:90px;outline:none;transition:border .15s;}
.form-textarea:focus{border-color:var(--brand-light);background:#fff;}
.btn-full{width:100%;padding:12px;font-size:14px;font-weight:600;cursor:pointer;border-radius:var(--radius-sm);border:none;font-family:'DM Sans',sans-serif;transition:all .18s;}
.btn-brand{background:var(--brand);color:#fff;}.btn-brand:hover{background:var(--brand-mid);}
.form-row-2{display:grid;grid-template-columns:1fr 1fr;gap:12px;}
/* Flex (not absolute) side-lines so a long label ("About: …") wraps instead of overlapping them */
.divider{display:flex;align-items:center;gap:10px;color:var(--text-faint);font-size:12px;margin:16px 0;text-align:center;overflow-wrap:anywhere;}
.divider::before,.divider::after{content:'';flex:1;min-width:24px;height:1px;background:var(--border);}
/* Chat date separators: plain centered label, no side lines (the auth modal's "or" keeps its lines) */
.date-divider{justify-content:center;}
.date-divider::before,.date-divider::after{display:none;}
.switch-link{text-align:center;font-size:13px;color:var(--text-muted);margin-top:14px;}
.switch-link a{color:var(--brand);font-weight:500;cursor:pointer;}
.login-error{background:var(--danger-pale);color:var(--danger);font-size:13px;padding:9px 12px;border-radius:var(--radius-sm);margin-bottom:12px;display:none;}
/* Signup consent row. Deliberately NOT .check-label — that one is align-items:center for
   single-line filter chips, which floats the box in the vertical middle once this text
   wraps to two lines. flex-start pins it to the first line. margin-top on the input is
   optical alignment: browsers give a checkbox no leading, so it sits ~2px high next to
   16px-tall text. */
.agree-row{display:flex;align-items:flex-start;gap:9px;font-size:13px;line-height:1.5;color:var(--text-muted);cursor:pointer;margin-bottom:14px;}
.agree-row input{margin:2px 0 0;flex-shrink:0;cursor:pointer;}
.agree-row a{color:var(--brand);font-weight:500;text-decoration:underline;}
/* Full-screen auth interstitial (set-a-new-password). Same shape as the older verify and
   suspension screens, but built from classes rather than inline styles — new UI uses
   styles.css. Hidden until JS adds .is-on. */
.auth-screen{display:none;position:fixed;inset:0;background:var(--bg);z-index:600;overflow-y:auto;}
.auth-screen.is-on{display:block;}
.auth-screen-inner{min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 24px;}
.auth-screen-card-wrap{max-width:440px;width:100%;}
.auth-brand{font-family:'DM Serif Display',serif;font-size:24px;color:var(--brand);margin-bottom:32px;text-align:center;}
.auth-brand span{color:var(--accent);}
.auth-card{background:var(--surface);border-radius:var(--radius);border:1px solid var(--border);padding:30px 28px;text-align:center;}
.auth-card-icon{height:38px;color:var(--brand);display:flex;align-items:center;justify-content:center;margin-bottom:14px;}
.auth-card-title{font-family:'DM Serif Display',serif;font-size:22px;margin-bottom:10px;}
.auth-card-body{font-size:14px;color:var(--text-muted);line-height:1.6;margin:0 0 20px;}
.auth-card-field{text-align:left;}

.is-hidden{display:none !important;}

/* What the owner of a rejected/removed listing sees in its detail view: the status, and
   the moderator's reason. Muted rather than alarming — it explains, it doesn't scold. */
.owner-moderated{text-align:center;padding:11px;border:1px solid var(--border);border-radius:var(--radius-sm);font-size:13px;color:var(--text-muted);}
.owner-moderated-reason{margin-top:6px;font-size:12px;color:var(--danger);line-height:1.5;}
.owner-appeal-btn{margin-top:10px;background:none;border:1px solid var(--border);border-radius:20px;padding:6px 16px;font-size:12.5px;font-weight:600;color:var(--text-muted);cursor:pointer;font-family:'DM Sans',sans-serif;}
.owner-appeal-btn:hover{border-color:var(--brand);color:var(--brand);}

/* The "check your email" panel that replaces the login form after a reset request. */
.reset-sent{display:none;text-align:center;padding:6px 0 2px;}
.reset-sent.is-on{display:block;}
.reset-sent-icon{height:34px;color:var(--brand);display:flex;align-items:center;justify-content:center;margin-bottom:12px;}
/* Maintenance screen — replaces two inline font-sizes that are now gone. */
.maint-icon{color:var(--text-faint);margin-bottom:20px;display:flex;justify-content:center;}
.reset-sent-title{font-family:'DM Serif Display',serif;font-size:20px;margin-bottom:10px;}
.reset-sent-body{font-size:13.5px;color:var(--text-muted);line-height:1.65;margin:0 0 20px;}
.reset-sent-body span{font-weight:600;color:var(--text);word-break:break-all;}

/* A <form> wrapper that exists purely to own its input for browser-autofill purposes,
   contributing no box of its own so the surrounding layout is unchanged. Form ownership
   is a DOM relationship, so display:contents does not weaken it. */
.form-shell{display:contents;}
/* "Not you?" — the escape hatch from the welcome-back greeting. Hidden by default and
   revealed with .is-on, so only a remembered student ever sees it. It clears the stored
   hint, which matters most on a shared campus computer, so it stays legible rather than
   being tucked away as faint small print. */
.login-notyou{display:none;text-align:center;font-size:12.5px;color:var(--text-muted);margin-top:10px;}
.login-notyou.is-on{display:block;}
.login-notyou a{color:var(--text-muted);font-weight:600;cursor:pointer;text-decoration:underline;text-underline-offset:2px;}
.login-notyou a:hover{color:var(--brand);}

/* ============================================================
   STUDENT INTERFACE
============================================================ */
#studentApp{display:block;}
/* NAV */
.s-nav{background:var(--brand);padding:0 28px;display:flex;align-items:center;justify-content:space-between;height:58px;position:sticky;top:0;z-index:100;}
.s-logo{font-family:'DM Serif Display',serif;color:#fff;font-size:22px;letter-spacing:-.5px;cursor:pointer;}
.s-logo span{color:var(--accent);}
.nav-links{display:flex;gap:6px;align-items:center;}
.nav-btn{padding:7px 16px;border-radius:var(--radius-sm);font-size:13px;font-weight:500;cursor:pointer;border:none;transition:all .18s;font-family:'DM Sans',sans-serif;}
.nav-ghost{background:transparent;color:rgba(255,255,255,0.8);}
.nav-ghost:hover{background:rgba(255,255,255,0.1);color:#fff;}
.nav-solid{background:var(--accent);color:var(--brand);}
.nav-solid:hover{background:#e09600;}
.nav-user{display:none;align-items:center;gap:10px;}
/* Avatars are painted by paintAvatarEl() as a background-image. The cover/center
   crop lives HERE, not in JS, so a stray `el.style.background = ...` can't wipe it
   and leave the photo un-scaled in the top-left corner. */
.nav-avatar{width:32px;height:32px;border-radius:50%;background:var(--brand-light);background-size:cover;background-position:center;background-repeat:no-repeat;color:#fff;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:600;cursor:pointer;}
/* ANNOUNCEMENT BANNER */
#sBanner{display:none;background:var(--accent);color:var(--brand);text-align:center;padding:9px 16px;font-size:13px;font-weight:500;}
/* PAGES */
.page{display:none;animation:fadeIn .22s ease;}
.page.active{display:block;}
@keyframes fadeIn{from{opacity:0;transform:translateY(8px);}to{opacity:1;transform:none;}}
/* HERO */
/* The hero on a phone: one column, smaller headline, no mock. 56px display type set on a
   390px screen breaks "One trusted hub" across three lines and pushes the buttons below the
   fold, so the first thing a visitor sees becomes a fragment of a sentence. */
@media(max-width:900px){
  .hero{gap:var(--sp-5);padding:var(--sp-6) var(--sp-4);}
  .hero-mock{display:none;}
  .hero h1{font-size:38px;letter-spacing:-.5px;}
  .hero-text{max-width:none;}
  .hero-text p{font-size:var(--fs-body);}
  .hero-cta{width:100%;}
  .hero-cta .btn-primary,.hero-cta .btn-outline{flex:1;min-width:200px;justify-content:center;}
}
@media(max-width:420px){
  .hero h1{font-size:32px;}
  .hero-cta .btn-primary,.hero-cta .btn-outline{min-width:100%;}
}

/* ------------------------------------------------------------------- HERO PHONE MOCK
   The product, drawn in CSS. Every colour is a token, so it repaints with the app instead of
   becoming a screenshot of a version that no longer exists. Purely decorative: aria-hidden in
   the markup, and not rendered at all on a phone. */
.hero-mock{flex:none;width:300px;height:470px;background:var(--surface);border-radius:30px;
  padding:9px;box-shadow:var(--shadow-lg);}
.hm-screen{width:100%;height:100%;border-radius:23px;overflow:hidden;background:var(--bg);
  display:flex;flex-direction:column;}
.hm-top{flex:none;height:46px;background:var(--brand);display:flex;align-items:center;
  justify-content:space-between;padding:0 14px;}
.hm-wordmark{font-family:'DM Serif Display',serif;font-size:15px;color:#fff;}
.hm-wordmark span{color:var(--accent);}
.hm-avatar{width:22px;height:22px;border-radius:50%;background:var(--brand-light);}
.hm-body{flex:1;min-height:0;overflow:hidden;padding:13px;display:flex;flex-direction:column;gap:12px;}
.hm-greet{font-family:'DM Serif Display',serif;font-size:19px;color:var(--text);line-height:1.15;}
.hm-chips{display:flex;gap:6px;}
.hm-chip{flex:1;height:50px;border-radius:11px;}
.hm-chip.c1{background:var(--cat-housing-bg);}
.hm-chip.c2{background:var(--cat-donation-bg);}
.hm-chip.c3{background:var(--cat-clothing-bg);}
.hm-chip.c4{background:var(--cat-technology-bg);}
.hm-card{background:var(--surface);border-radius:13px;overflow:hidden;box-shadow:var(--shadow-sm);}
.hm-card-img{height:96px;background:linear-gradient(150deg,var(--cat-housing-bg) 0%,#BFCFC2 100%);}
.hm-card-body{padding:9px 11px 11px;display:flex;flex-direction:column;align-items:flex-start;gap:5px;}
.hm-card-tag{background:var(--cat-housing-bg);color:var(--cat-housing-text);font-size:8.5px;
  font-weight:700;padding:2px 7px;border-radius:var(--radius-pill);}
.hm-card-title{font-size:12px;font-weight:600;color:var(--text);}
.hm-card-price{font-family:'DM Serif Display',serif;font-size:16px;color:var(--brand);line-height:1;}
.hm-club{background:var(--accent-pale);border-radius:13px;padding:10px 12px;display:flex;
  align-items:center;justify-content:space-between;}
.hm-club-title{font-family:'DM Serif Display',serif;font-size:13px;color:var(--brand);}
.hm-club-btn{height:26px;padding:0 12px;border-radius:var(--radius-pill);background:var(--accent-warm);
  color:#fff;font-size:10.5px;font-weight:600;display:flex;align-items:center;}
.hm-tabs{flex:none;height:44px;background:var(--surface);border-top:1px solid var(--border);
  display:flex;align-items:center;justify-content:space-around;padding:0 6px;}
.hm-tab{width:17px;height:17px;border-radius:4px;background:var(--border);}
.hm-tab.on{background:var(--brand);}
.hm-plus{width:32px;height:32px;border-radius:50%;background:var(--brand);}

/* ------------------------------------------------------------------------------ HERO
   Was a dark green slab with centred white text. It is now the cream ground the rest of the
   app stands on, with the product beside the words. The reason is not decoration: a green
   slab was the first thing a visitor saw and nothing else in the app looked like it, so the
   landing page and the product read as two different products. */
.hero{background:var(--bg);padding:var(--sp-7) var(--sp-5) var(--sp-7);display:flex;
  align-items:center;justify-content:center;gap:64px;flex-wrap:wrap;}
.hero-text{flex:1;min-width:0;max-width:560px;display:flex;flex-direction:column;align-items:flex-start;}
.hero-badge{display:inline-flex;align-items:center;gap:var(--sp-2);background:var(--brand-pale);
  color:var(--cat-housing-text);font-size:var(--fs-small);font-weight:600;padding:8px 16px;
  border-radius:var(--radius-pill);margin-bottom:var(--sp-5);}
.hero h1{font-family:'DM Serif Display',serif;color:var(--text);font-size:56px;line-height:1.08;
  letter-spacing:-1px;margin-bottom:var(--sp-4);}
/* Italic, and terracotta rather than the yellow it used to be. On cream, yellow text is close
   to unreadable — it only worked because it sat on a dark green slab. */
.hero h1 em{color:var(--accent-warm);font-style:italic;}
.hero-text p{color:var(--text-muted);font-size:var(--fs-lead);max-width:480px;line-height:1.7;margin:0;}
.hero-cta{display:flex;gap:14px;flex-wrap:wrap;margin-top:var(--sp-5);}
/* Specificity, not !important. These are <p> like the lead is, so they need to outrank
   .hero-text p — one extra class does that and stays overridable later. */
/* --text-muted, not --text-faint. Faint measures 2.9:1 on cream, which is below AA, and this
   line is the page's actual promise — every member is .edu verified — not decoration. A trust
   signal nobody can read is not a trust signal. (--text-faint fails AA wherever it carries
   text; that is a whole-app problem for the accessibility pass, not just this line.) */
.hero-text .hero-trust{display:flex;align-items:center;gap:9px;margin-top:var(--sp-5);
  font-size:13.5px;color:var(--text-muted);}
.hero-text .hero-byline{margin-top:var(--sp-5);font-size:var(--fs-small);color:var(--text-muted);}

/* The primary action is brand green, not the accent yellow. Yellow on cream fails contrast
   for the label; it was legible before only because the slab behind it was dark. */
.btn-primary{background:var(--brand);color:#fff;padding:0 30px;height:54px;border-radius:var(--radius-pill);
  font-weight:600;font-size:15.5px;cursor:pointer;border:none;font-family:'DM Sans',sans-serif;
  display:inline-flex;align-items:center;gap:9px;box-shadow:0 6px 20px rgba(26,58,42,.24);
  transition:background var(--dur) var(--ease),transform var(--dur) var(--ease);}
.btn-primary:hover{background:var(--brand-mid);transform:translateY(-1px);}
.btn-primary:focus-visible{outline:var(--focus-ring);outline-offset:var(--focus-offset);}
.btn-outline{background:transparent;color:var(--text);padding:0 26px;height:54px;
  border-radius:var(--radius-pill);font-weight:500;font-size:15.5px;cursor:pointer;
  border:1px solid var(--border);font-family:'DM Sans',sans-serif;display:inline-flex;
  align-items:center;transition:border-color var(--dur) var(--ease),background var(--dur) var(--ease);}
.btn-outline:hover{border-color:var(--brand-light);background:var(--bg-alt);}
.btn-outline:focus-visible{outline:var(--focus-ring);outline-offset:var(--focus-offset);}
.stat{text-align:center;}
.stat-num{font-family:'DM Serif Display',serif;color:var(--accent);font-size:32px;}
.stat-label{color:rgba(255,255,255,0.55);font-size:12px;margin-top:2px;}
/* LISTINGS PAGE */
.page-wrap{max-width:980px;margin:0 auto;padding:36px 24px;}
.page-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:28px;flex-wrap:wrap;gap:12px;}
.page-title{font-family:'DM Serif Display',serif;font-size:28px;}
.btn-sm-s{padding:9px 18px;border-radius:var(--radius-sm);font-size:13px;font-weight:500;cursor:pointer;border:none;font-family:'DM Sans',sans-serif;transition:all .18s;}
.btn-green{background:var(--brand);color:#fff;}.btn-green:hover{background:var(--brand-mid);}
.filters{display:flex;gap:8px;margin-bottom:14px;flex-wrap:wrap;}
.filter-chip{padding:7px 14px;border-radius:20px;font-size:13px;cursor:pointer;border:1px solid var(--border);background:var(--surface);color:var(--text-muted);transition:all .15s;font-family:'DM Sans',sans-serif;}
.filter-chip:hover,.filter-chip.active{background:var(--brand);color:#fff;border-color:var(--brand);}
.active-filter-tag{display:inline-flex;align-items:center;gap:4px;background:var(--bg);border:1px solid var(--border);color:var(--text-muted);border-radius:20px;padding:3px 8px 3px 11px;font-size:12px;font-weight:500;}
.active-filter-tag button{background:none;border:none;cursor:pointer;color:var(--text-faint);font-size:15px;line-height:1;padding:0;font-weight:600;font-family:inherit;}
.active-filter-tag button:hover{color:var(--danger);}
/* Category tab strip — typographic, underline on selected (no emoji, no fill) */
/* ---------------------------------------------------------------- MARKETPLACE MASTHEAD
   The title is a serif display face because it is the one line on the page that is not a
   control — everything under it is something to tap. Sizes come from the Phase 0 scale so
   this heading is the same size as every other page's heading.

   .mk-search is a <button> that looks like an input, and that is deliberate. Tapping it
   opens the Search page, which owns the query, the recent-search history and the results.
   A real <input> here would raise the keyboard and then navigate out from under it. */
.mk-head{margin-bottom:var(--sp-4);}
.mk-title{font-family:'DM Serif Display',serif;font-size:var(--fs-display);line-height:1.08;color:var(--text);}
.mk-sub{font-size:var(--fs-lead);color:var(--text-muted);margin-top:var(--sp-1);}

.mk-tools{display:flex;gap:var(--sp-3);margin-bottom:var(--sp-4);}
.mk-search{flex:1;min-width:0;height:50px;background:var(--surface);border:1px solid var(--border);
  border-radius:var(--radius-pill);display:flex;align-items:center;gap:var(--sp-3);padding:0 18px;
  cursor:pointer;font-family:inherit;color:var(--text-faint);text-align:left;
  transition:border-color var(--dur) var(--ease),box-shadow var(--dur) var(--ease);}
.mk-search:hover{border-color:var(--brand-light);}
.mk-search:focus-visible{outline:var(--focus-ring);outline-offset:var(--focus-offset);}
.mk-search-ph{font-size:var(--fs-body);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}

.mk-filter{flex:none;width:50px;height:50px;border-radius:15px;background:var(--brand-pale);
  color:var(--brand-mid);border:none;cursor:pointer;display:flex;align-items:center;
  justify-content:center;position:relative;transition:background var(--dur) var(--ease);}
.mk-filter:hover{background:var(--cat-housing-bg);}
.mk-filter:hover svg circle{fill:var(--cat-housing-bg);}
.mk-filter:focus-visible{outline:var(--focus-ring);outline-offset:var(--focus-offset);}
/* The circles in the slider glyph are filled with the button's own background, so the
   horizontal line reads as passing behind them rather than through them. */
.mk-filter svg circle{fill:var(--brand-pale);}
.mk-filter-count{position:absolute;top:-4px;right:-4px;min-width:19px;height:19px;padding:0 5px;
  border-radius:var(--radius-pill);background:var(--accent-warm);color:#fff;
  font-size:var(--fs-micro);font-weight:700;line-height:19px;text-align:center;}

/* On a phone the + tab in the bottom bar already posts, so this button is a second door to
   the same room and only costs height. It earns its place on desktop, where there is no tab bar. */
.mk-post{flex:none;}

/* ------------------------------------------------------------------ CATEGORY PILL ROW
   One compact row that scrolls sideways, per the approved mockup. (The club directory's filter row used the old .cat-tab underline style
   until 5.3a moved it to chips; those rules went with it.)

   Each pill names its own two colours through --pill-bg / --pill-fg, so the shared rules
   below stay at three, and selecting a pill just swaps which of its two colours is the
   background. The values come from the --cat-* tokens, which are the single source of
   truth for category colour — CATEGORY_COLORS in config.js only names these variables. */
/* No negative margin bleeding this row to the screen edge. .page-wrap's side padding
   changes across breakpoints, so a fixed pull would either fall short or push the row
   wider than the viewport — and a page that scrolls sideways is a worse outcome than a
   row that starts where the rest of the content starts. */
.cat-row-wrap{position:relative;margin-bottom:var(--sp-4);}
.cat-row{display:flex;gap:var(--sp-2);overflow-x:auto;scrollbar-width:none;padding:2px 0;}
.cat-row::-webkit-scrollbar{display:none;}
/* Sits over the right edge so the row looks cut off rather than finished. pointer-events
   none, or it would swallow taps on the pill underneath it. */
.cat-row-wrap::after{content:'';position:absolute;top:0;right:0;width:32px;height:100%;
  pointer-events:none;background:linear-gradient(90deg,transparent,var(--bg) 78%);}

.cat-pill{flex:none;height:34px;padding:0 13px;border-radius:var(--radius-pill);border:none;
  display:inline-flex;align-items:center;gap:6px;cursor:pointer;white-space:nowrap;
  font-family:'DM Sans',sans-serif;font-size:12.5px;font-weight:600;
  background:var(--cat-tint);color:var(--cat-deep);
  transition:background var(--dur) var(--ease),color var(--dur) var(--ease);}
.cat-pill.active{background:var(--cat-deep);color:#fff;}
.cat-pill:focus-visible{outline:var(--focus-ring);outline-offset:var(--focus-offset);}

/* ONE mapping from category to its two colours, read by anything carrying data-cat: the
   pills above, and the listing card's tint panel and badge. Custom properties do nothing on
   their own, so a bare attribute selector here is safe — an element only picks up a colour
   when a rule actually asks for var(--cat-tint) or var(--cat-deep). */
[data-cat="all"]       {--cat-tint:var(--bg-alt);                  --cat-deep:var(--text-muted);}
[data-cat="housing"]   {--cat-tint:var(--cat-housing-bg);          --cat-deep:var(--cat-housing-text);}
[data-cat="clothing"]  {--cat-tint:var(--cat-clothing-bg);         --cat-deep:var(--cat-clothing-text);}
[data-cat="technology"]{--cat-tint:var(--cat-technology-bg);       --cat-deep:var(--cat-technology-text);}
[data-cat="donation"]  {--cat-tint:var(--cat-donation-bg);         --cat-deep:var(--cat-donation-text);}
[data-cat="books"]     {--cat-tint:var(--cat-books-bg);            --cat-deep:var(--cat-books-text);}
[data-cat="other"]     {--cat-tint:var(--cat-other-bg);            --cat-deep:var(--cat-other-text);}
/* Legacy rows only — this category can no longer be posted, but old listings still carry it. */
[data-cat="organization_event"]{--cat-tint:var(--cat-organization_event-bg);--cat-deep:var(--cat-organization_event-text);}
.cat-pill[data-cat="all"]{padding:0 15px;}
.cat-pill[data-cat="all"].active{--cat-deep:var(--brand);}

/* Primary Filters button */
.filters-btn{display:inline-flex;align-items:center;gap:7px;padding:9px 15px;border-radius:var(--radius-sm);font-size:13px;font-weight:500;cursor:pointer;border:1px solid var(--border);background:var(--surface);color:var(--text);font-family:'DM Sans',sans-serif;flex-shrink:0;transition:border-color .15s,background .15s;}
.filters-btn:hover{border-color:var(--brand);}
.filters-btn.open{border-color:var(--brand);background:var(--brand-pale);color:var(--brand);}
.filters-btn-count{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 5px;border-radius:9px;background:var(--brand);color:#fff;font-size:11px;font-weight:600;}
.school-badge{display:inline-block;background:#eef2ff;border:1px solid #c7d2fe;color:#4338ca;border-radius:10px;padding:2px 7px;font-size:10px;font-weight:600;letter-spacing:.02em;white-space:nowrap;}
.school-dist{font-weight:400;opacity:.75;font-size:9px;margin-left:2px;}
/* DEEP FILTER PANEL */
.df-panel{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;margin-bottom:16px;box-shadow:0 2px 8px rgba(26,58,42,0.06);}
.df-section{border-bottom:1px solid var(--border);}
.df-section:last-child{border-bottom:none;}
.df-toggle{display:flex;align-items:center;justify-content:space-between;width:100%;padding:11px 16px;background:none;border:none;cursor:pointer;font-size:13px;font-weight:600;color:var(--text);font-family:'DM Sans',sans-serif;text-align:left;transition:background .15s;}
.df-toggle:hover{background:var(--bg);}
.df-chevron{font-size:16px;color:var(--text-muted);transition:transform .2s;line-height:1;}
.df-body{padding:4px 16px 16px;}
.df-static-label{padding:12px 16px 2px;font-size:13px;font-weight:600;color:var(--text);}
.price-range-wrap{position:relative;height:38px;margin:8px 0 6px;}
.price-range-wrap input[type="range"]{position:absolute;width:100%;pointer-events:none;-webkit-appearance:none;appearance:none;background:transparent;height:4px;top:50%;transform:translateY(-50%);margin:0;padding:0;}
.price-range-wrap input[type="range"]::-webkit-slider-thumb{pointer-events:all;-webkit-appearance:none;width:20px;height:20px;border-radius:50%;background:var(--brand);cursor:pointer;border:2px solid #fff;box-shadow:0 1px 5px rgba(0,0,0,.2);}
.price-range-wrap input[type="range"]::-moz-range-thumb{pointer-events:all;width:20px;height:20px;border-radius:50%;background:var(--brand);cursor:pointer;border:2px solid #fff;box-shadow:0 1px 5px rgba(0,0,0,.2);border:none;}
.price-range-track{position:absolute;height:4px;top:50%;transform:translateY(-50%);left:0;right:0;background:var(--border);border-radius:2px;pointer-events:none;}
.price-range-fill{position:absolute;height:4px;background:var(--brand);border-radius:2px;}
.price-labels{display:flex;justify-content:space-between;font-size:12px;color:var(--text-muted);margin-top:2px;font-weight:500;}
.df-chips{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px;}
.df-chips .filter-chip{padding:5px 12px;font-size:12px;}
.active-filter-tag{animation:tagIn .15s ease;}
@keyframes tagIn{from{opacity:0;transform:scale(.92);}to{opacity:1;transform:scale(1);}}
/* FILTER PANEL / DRAWER */
.filter-drawer-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.12);z-index:480;opacity:0;transition:opacity .25s;pointer-events:none;}
.filter-drawer-backdrop.open{opacity:1;pointer-events:all;}
/* Desktop: slide-in panel from the right */
.filter-drawer{position:fixed;top:0;right:0;bottom:0;width:400px;max-width:92vw;background:var(--surface);z-index:490;display:flex;flex-direction:column;transform:translateX(100%);transition:transform .28s cubic-bezier(.4,0,.2,1);box-shadow:-4px 0 32px rgba(0,0,0,.12);}
.filter-drawer.open{transform:translateX(0);}
.filter-drawer-grip{display:none;padding:12px 0 4px;justify-content:center;cursor:grab;flex-shrink:0;}
.filter-drawer-grip-bar{width:36px;height:4px;background:var(--border);border-radius:2px;}
.filter-drawer-header{display:flex;align-items:center;justify-content:space-between;padding:4px 20px 12px;flex-shrink:0;border-bottom:1px solid var(--border);}
.filter-drawer-title{font-size:16px;font-weight:600;color:var(--text);}
.filter-drawer-close{background:none;border:none;cursor:pointer;font-size:22px;color:var(--text-muted);padding:4px;line-height:1;font-family:inherit;}
.filter-drawer-close:hover{color:var(--text);}
.filter-drawer-body{overflow-y:auto;flex:1;padding:16px 20px 0;}
.filter-drawer-footer{padding:16px 20px;border-top:1px solid var(--border);flex-shrink:0;}
@media(max-width:640px){
  /* Mobile: drawer becomes a bottom sheet */
  .filter-drawer{top:auto;left:0;right:0;bottom:0;width:auto;max-width:none;max-height:85vh;border-radius:18px 18px 0 0;transform:translateY(100%);box-shadow:0 -4px 32px rgba(0,0,0,.14);}
  .filter-drawer.open{transform:translateY(0);}
  .filter-drawer-grip{display:flex;}
  .filter-drawer-backdrop{background:rgba(0,0,0,.35);}
  /* The bottom bar's + already posts, so this button is a second door to the same room. */
  .mk-post{display:none;}
  .mk-title{font-size:var(--fs-title);}
  .mk-sub{font-size:var(--fs-body);}
  .mk-search,.mk-filter{height:46px;}
  .mk-filter{width:46px;}
}
.cat-pick-btn{display:flex;align-items:center;justify-content:center;min-height:64px;padding:18px 12px;border-radius:var(--radius);border:1px solid transparent;cursor:pointer;font-family:'DM Sans',sans-serif;font-size:15px;font-weight:600;transition:transform .12s,box-shadow .15s;}
.cat-pick-btn:hover{transform:translateY(-2px);box-shadow:0 4px 14px rgba(0,0,0,.08);}
/* Pinterest-style masonry via CSS multi-column: cards keep natural heights, next card slots up — no row gaps. */
/* ------------------------------------------------------------------- MARKET SECTIONS
   Featured, then At your school, then From nearby campuses — each with its own grid and its
   own See-all. A section with nothing in it is not drawn at all, so these rules never have
   to describe an empty state. */
.mk-sec{margin-bottom:var(--sp-6);}
.mk-sec:last-child{margin-bottom:0;}
.mk-sec-head{display:flex;align-items:baseline;gap:var(--sp-2);margin-bottom:var(--sp-3);}
.mk-sec-title{font-family:'DM Serif Display',serif;font-size:var(--fs-title);color:var(--brand);line-height:1.2;}
.mk-sec-meta{font-size:var(--fs-small);color:var(--text-faint);white-space:nowrap;}
/* Pushed to the right edge of the heading row, and kept on one line — "Show less" is wider
   than "See all", and a button that changes width when tapped reads as the layout twitching. */
.mk-sec-more{margin-left:auto;flex:none;display:inline-flex;align-items:center;gap:var(--sp-1);
  background:none;border:none;padding:var(--sp-1) 0;cursor:pointer;white-space:nowrap;
  font-family:'DM Sans',sans-serif;font-size:var(--fs-small);font-weight:600;color:var(--accent-warm);}
.mk-sec-more:hover{color:var(--text);}
.mk-sec-more:focus-visible{outline:var(--focus-ring);outline-offset:var(--focus-offset);}
/* One chevron, turned over, rather than a second icon that has to be kept in step with it. */
.mk-sec-more svg{transition:transform var(--dur) var(--ease);}
.mk-sec-more.open svg{transform:rotate(180deg);}

.lc-empty-icon{margin-bottom:var(--sp-3);color:var(--text-faint);}
.lc-empty-title{font-size:var(--fs-body);font-weight:500;margin-bottom:var(--sp-2);color:var(--text);}
.lc-empty-sub{font-size:var(--fs-small);margin-bottom:var(--sp-4);}
.lc-empty-btn{background:none;border:1px solid var(--border);border-radius:var(--radius-pill);
  padding:var(--sp-2) 18px;font-size:var(--fs-small);color:var(--text-muted);cursor:pointer;font-family:inherit;}
.lc-empty-btn:hover{border-color:var(--brand-light);color:var(--text);}

/* A real grid, not CSS multi-column. The old masonry let every card be its own height,
   which is precisely why two columns could never line up: column-count fills column one to
   the bottom before starting column two, so "2 columns" read as one long strip beside
   another. Cards are now a fixed shape and sit in rows.
   Two columns is the floor, including on the narrowest phone — the approved design is a
   scannable 2-up, and dropping to a single column would turn it back into a list. */
.listings-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:18px;align-items:start;}
@media(max-width:1100px){.listings-grid{grid-template-columns:repeat(2,minmax(0,1fr));}}
@media(max-width:680px){.listings-grid{gap:12px;}}
/* PINNED SECTION */
/* LISTING CARD */
.listing-card{background:var(--surface);border-radius:var(--radius-lg);border:1px solid var(--border);
  overflow:hidden;cursor:pointer;display:flex;flex-direction:column;box-shadow:var(--shadow-sm);
  transition:transform var(--dur) var(--ease),box-shadow var(--dur) var(--ease),border-color var(--dur) var(--ease);}
.listing-card:hover{transform:translateY(-3px);border-color:var(--brand-light);box-shadow:var(--shadow-md);}
.listing-card:focus-visible{outline:2px solid var(--brand-light);outline-offset:2px;}
.listing-card.pinned-card{border:2px solid rgba(107,33,168,0.3);box-shadow:0 0 0 3px rgba(107,33,168,0.06);}
/* Redesigned card — Instagram structure: poster header on top, photo-led hero, info below */
.poster-head{display:flex;align-items:center;gap:9px;padding:11px 13px;}
.trust-official{font-size:10px;font-weight:700;color:#fff;background:var(--pin);padding:1px 7px;border-radius:10px;letter-spacing:.02em;}
/* Both heroes are 4:3 so a row of cards lines up. aspect-ratio scales with the column, so
   this holds from a 170px phone card to a 300px desktop one without a second breakpoint. */
.lc-photo{position:relative;line-height:0;aspect-ratio:4/3;max-width:100%;background:var(--cat-tint,var(--bg-alt));}
.lc-photo-img{width:100%;height:100%;display:block;object-fit:cover;}
.lc-count{position:absolute;bottom:8px;right:8px;background:rgba(0,0,0,.6);color:#fff;font-size:11px;font-weight:600;padding:2px 8px;border-radius:12px;line-height:1.4;}
.lc-noimg{position:relative;aspect-ratio:4/3;max-width:100%;padding:14px;display:flex;align-items:flex-end;
  background:var(--cat-tint,var(--bg-alt));color:var(--cat-deep,var(--text));}
.lc-noimg-title{font-family:'DM Serif Display',serif;font-size:var(--fs-heading);line-height:1.15;
  display:-webkit-box;-webkit-line-clamp:3;line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;}
.lc-body{padding:10px 12px 13px;display:flex;flex-direction:column;align-items:flex-start;gap:6px;flex:1;}
.lc-badge{background:var(--cat-tint,var(--bg-alt));color:var(--cat-deep,var(--text-muted));
  font-size:10px;font-weight:700;padding:3px 9px;border-radius:var(--radius-pill);}
.lc-title{font-size:13.5px;font-weight:600;line-height:1.3;display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;}
/* The pin is a flex item and the place name is its own span, so the span is what truncates.
   Ellipsis cannot apply to a bare text node sitting directly in a flex container, and a long
   pickup spot on a 170px card is the normal case, not the edge one. align-self:stretch is
   needed because .lc-body packs its children to the start. */
.lc-loc{color:var(--text-muted);font-size:11.5px;display:flex;align-items:center;gap:4px;
  align-self:stretch;min-width:0;max-width:100%;}
.lc-loc span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;}
.lc-title{align-self:stretch;}
/* Pushed to the bottom so price sits on one line across a row of uneven titles. */
.lc-price{margin-top:auto;}
.lc-price{font-family:'DM Serif Display',serif;font-size:var(--fs-lead);color:var(--brand);line-height:1;}
.lc-price span{font-family:'DM Sans',sans-serif;font-size:12px;color:var(--text-muted);font-weight:400;}
.lc-soft{font-family:'DM Serif Display',serif;font-size:18px;}
/* The save control sits ON the hero, over an opaque disc. It used to sit in the footer,
   with a comment saying a control over an image is invisible on a light photo — true of a
   bare icon, which is why this one has a solid backing of its own. The footer it lived in
   no longer exists. */
.lc-fav{position:absolute;top:8px;right:8px;width:30px;height:30px;border-radius:50%;
  background:rgba(255,255,255,.94);color:var(--text-muted);box-shadow:var(--shadow-xs);z-index:1;}
.lc-fav svg{width:15px;height:15px;}
.pin-badge-pending{background:var(--accent-warm);}

/* ---- LOADING SKELETONS -------------------------------------------------
   Grey placeholder cards shown while the feed's listings are still in flight
   from Supabase. They copy the real card's shape — avatar, hero, two text
   lines, price — so nothing jumps when the real cards replace them.
   A light band sweeps across each block; that motion is what tells a student
   the page is working rather than frozen. Painted by feedSkeletonHTML() in
   js/listings.js and overwritten by renderListings(). */
.sk-card{background:var(--surface);border-radius:var(--radius-lg);border:1px solid var(--border);overflow:hidden;}
.sk-body{padding:10px 12px 13px;display:flex;flex-direction:column;align-items:flex-start;gap:7px;}

/* One shimmer rule covers every grey block, whatever its shape. */
.sk{position:relative;overflow:hidden;background:#eceff0;border-radius:6px;}
.sk::after{content:'';position:absolute;inset:0;transform:translateX(-100%);
  background:linear-gradient(90deg,transparent,rgba(255,255,255,.7),transparent);
  animation:sk-sweep 1.6s ease-in-out infinite;}
@keyframes sk-sweep{100%{transform:translateX(100%);}}

/* Each piece is named for the real element it stands in for. */
/* Each block stands in for the real element at the same size, so nothing shifts on load. */
.sk-hero{aspect-ratio:4/3;max-width:100%;border-radius:0;}
.sk-badge{width:54px;height:16px;border-radius:var(--radius-pill);}
.sk-title{width:88%;height:12px;}
.sk-sub{width:56%;height:10px;}
.sk-price{width:52px;height:16px;}

/* --- Messages: conversation-list rows. Mirrors .convo-item's box exactly. --- */
.sk-convo{display:flex;align-items:center;gap:12px;padding:14px 16px;border-bottom:1px solid var(--border);}
.sk-convo-avatar{width:40px;height:40px;border-radius:50%;flex-shrink:0;}
.sk-convo-info{flex:1;min-width:0;}
.sk-convo-name{width:118px;height:11px;margin-bottom:7px;}
.sk-convo-preview{width:172px;height:9px;}
.sk-convo-time{width:32px;height:9px;flex-shrink:0;}

/* --- Messages: thread bubbles. Three widths so it reads as real back-and-forth. --- */
.sk-thread{padding:18px 24px;display:flex;flex-direction:column;gap:12px;}
.sk-bubble{height:38px;border-radius:16px 4px 16px 16px;align-self:flex-end;}
.sk-bubble-theirs{border-radius:4px 16px 16px 16px;align-self:flex-start;}
.sk-bubble-s{width:38%;}
.sk-bubble-m{width:54%;}
.sk-bubble-l{width:68%;}

/* Students who asked their OS for less motion get the shapes without the sweep. */
@media(prefers-reduced-motion:reduce){.sk::after{animation:none;}}

/* ---- EMPTY STATES ------------------------------------------------------
   One shape for "there is genuinely nothing here yet", so every empty panel
   in the student app reads the same: an icon, a line that says what's missing,
   a line that says what to do, and (where there is somewhere to go) a button.
   The browse feed's empty state at js/listings.js is the pattern this copies. */
.empty-state{text-align:center;padding:44px 20px;color:var(--text-faint);}
.empty-state-icon{margin-bottom:12px;color:var(--text-faint);}
.empty-state-title{font-size:15px;font-weight:500;margin-bottom:6px;color:var(--text);}
.empty-state-sub{font-size:13px;line-height:1.5;margin-bottom:16px;}
.empty-state-btn{background:none;border:1px solid var(--border);border-radius:20px;padding:7px 18px;font-size:13px;color:var(--text-muted);cursor:pointer;font-family:inherit;transition:all .15s;}
.empty-state-btn:hover{border-color:var(--brand-light);color:var(--brand);}

img{image-orientation:from-image;}
/* Moved to the LEFT: top-right is the save control's corner now, and the two overlapped. */
.pin-badge{position:absolute;top:8px;left:8px;background:var(--pin);color:#fff;font-size:10px;
  font-weight:700;padding:3px 9px;border-radius:var(--radius-pill);z-index:1;}
.poster-name{font-size:12px;font-weight:500;}
/* MESSAGES */
.msg-layout{display:flex;height:calc(100vh - 58px);}
.msg-sidebar{width:300px;flex-shrink:0;background:var(--surface);border-right:1px solid var(--border);display:flex;flex-direction:column;transition:width .2s ease;overflow:hidden;}
.msg-sidebar.msg-collapsed{width:0;border-right:none;}
.msg-sidebar-header{padding:20px 16px 14px;border-bottom:1px solid var(--border);display:flex;align-items:flex-start;justify-content:space-between;gap:8px;}
.msg-sidebar-title{font-family:'DM Serif Display',serif;font-size:20px;margin-bottom:10px;}
.msg-toggle-btn{background:none;border:none;cursor:pointer;color:var(--text-muted);font-size:18px;padding:2px 5px;line-height:1;border-radius:4px;flex-shrink:0;margin-top:2px;transition:color .15s;}
.msg-toggle-btn:hover{color:var(--brand);}
.msg-reopen-btn{display:none;align-items:center;justify-content:center;background:none;border:1px solid var(--border);border-radius:6px;cursor:pointer;color:var(--text-muted);font-size:16px;padding:3px 8px;line-height:1;transition:all .15s;flex-shrink:0;}
.msg-reopen-btn:hover{background:var(--brand-pale);color:var(--brand);border-color:var(--brand-light);}
.msg-sidebar.msg-collapsed~.chat-area .msg-reopen-btn{display:flex;}
.search-input{width:100%;padding:8px 14px;border:1px solid var(--border);border-radius:20px;font-size:13px;background:var(--bg);outline:none;font-family:'DM Sans',sans-serif;}
.convo-list{flex:1;overflow-y:auto;}
.convo-item{display:flex;align-items:center;gap:12px;padding:14px 16px;cursor:pointer;border-bottom:1px solid var(--border);transition:background .12s;}
.convo-item:hover,.convo-item.active-convo{background:var(--brand-pale);}
.convo-avatar{width:40px;height:40px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:600;color:#fff;flex-shrink:0;}
.convo-info{flex:1;min-width:0;}
.convo-name{font-size:14px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.convo-preview{font-size:12px;color:var(--text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:2px;}
.convo-preview.unread{color:var(--text);font-weight:600;}
.convo-meta{display:flex;flex-direction:column;align-items:flex-end;gap:4px;}
.convo-time{font-size:11px;color:var(--text-faint);}
.convo-badge{background:var(--brand);color:#fff;font-size:10px;font-weight:600;width:18px;height:18px;border-radius:50%;display:flex;align-items:center;justify-content:center;}
.chat-area{display:flex;flex-direction:column;background:var(--bg);touch-action:pan-y;}
.chat-header{background:var(--surface);padding:16px 24px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:12px;}
.m-back{display:none;background:none;border:none;cursor:pointer;color:var(--text);padding:2px;margin:0 -2px 0 -6px;}
.chat-header-name{font-weight:600;font-size:15px;}
.chat-messages{flex:1;overflow-y:auto;padding:24px 24px 16px;display:flex;flex-direction:column;gap:14px;}
.msg-row{display:flex;}
.msg-row.mine{justify-content:flex-end;}
.bubble{min-width:0;max-width:75%;padding:10px 14px;border-radius:16px;font-size:14px;line-height:1.5;overflow-wrap:anywhere;}
.bubble.theirs{background:var(--surface);border-radius:4px 16px 16px 16px;border:1px solid var(--border);}
.bubble.mine{background:var(--brand);color:#fff;border-radius:16px 4px 16px 16px;}
/* Timestamp (and later, read ticks) tucked inside the bubble's bottom-right, WhatsApp-style */
.bubble-meta{float:right;font-size:10.5px;line-height:1;opacity:.65;white-space:nowrap;margin:9px -2px 0 8px;}
/* Read ticks (own messages only): ✓ = delivered to server, ✓✓ tinted = seen */
.ticks{margin-left:4px;letter-spacing:-1px;}
.ticks.seen{color:var(--accent);}
/* REPLIES: quoted block inside a reply bubble (tap → jump to original) */
.bubble-quote{display:block;border-left:3px solid var(--brand);background:rgba(0,0,0,.05);border-radius:6px;padding:6px 9px;margin:0 0 6px;font-size:12px;line-height:1.4;cursor:pointer;}
.bubble.mine .bubble-quote{border-left-color:#fff;background:rgba(255,255,255,.16);}
.bq-name{display:block;font-weight:600;font-size:11px;margin-bottom:1px;}
.bubble.theirs .bq-name{color:var(--brand);}
.bq-text{display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;overflow-wrap:anywhere;}
/* Desktop reply affordance: ↩ fades in beside a hovered bubble (hidden on touch layouts) */
.reply-hover{opacity:0;background:none;border:none;cursor:pointer;color:var(--text-faint);padding:0 6px;align-self:center;transition:opacity .15s,color .15s;}
.msg-row:hover .reply-hover{opacity:1;}
.reply-hover:hover{color:var(--brand);}
.msg-row.mine .reply-hover{order:-1;}
/* Composing state: preview of the message being replied to, pinned above the input */
.reply-bar{display:flex;align-items:center;gap:8px;background:var(--surface);border-top:1px solid var(--border);padding:8px 16px 0;}
.reply-bar-body{flex:1;min-width:0;border-left:3px solid var(--brand);background:var(--bg);border-radius:6px;padding:6px 10px;font-size:12px;}
.reply-bar-body .bq-name{color:var(--brand);}
.reply-bar-body .bq-text{-webkit-line-clamp:1;line-clamp:1;}
.reply-bar-x{background:none;border:none;cursor:pointer;font-size:19px;color:var(--text-muted);padding:2px 6px;line-height:1;}
/* Brief halo on the original message after tapping a quote */
.msg-flash .bubble{animation:msgflash 1.2s ease;}
@keyframes msgflash{0%,55%{box-shadow:0 0 0 3px var(--accent);}100%{box-shadow:0 0 0 3px transparent;}}
.chat-input-area{background:var(--surface);padding:16px 24px;border-top:1px solid var(--border);display:flex;gap:10px;align-items:flex-end;}
.composer-plus{background:none;border:none;cursor:pointer;color:var(--text-muted);width:40px;height:40px;border-radius:50%;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:background .15s,color .15s;}
.composer-plus:hover{background:var(--brand-pale);color:var(--brand);}
/* LISTING SHARE PICKER: bottom sheet on phones, centered panel on desktop */
.lp-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.35);z-index:300;}
.lp-sheet{position:fixed;left:50%;bottom:0;transform:translateX(-50%);width:min(480px,100%);max-height:70vh;background:var(--surface);border-radius:16px 16px 0 0;z-index:301;display:flex;flex-direction:column;gap:10px;padding:18px 16px calc(14px + env(safe-area-inset-bottom));box-shadow:0 -8px 30px rgba(0,0,0,.18);}
@media(min-width:769px){.lp-sheet{bottom:auto;top:50%;transform:translate(-50%,-50%);border-radius:16px;max-height:60vh;}}
.lp-head{display:flex;align-items:center;justify-content:space-between;gap:10px;}
.lp-title{font-family:'DM Serif Display',serif;font-size:18px;}
.lp-view-btns{display:flex;gap:5px;}
.lp-view-btn{background:none;border:1px solid var(--border);border-radius:7px;width:29px;height:29px;display:flex;align-items:center;justify-content:center;cursor:pointer;color:var(--text-muted);transition:all .15s;}
.lp-view-btn.active{background:var(--brand);border-color:var(--brand);color:#fff;}
/* Category-colored icon tile — app-wide no-photo fallback for small thumbs */
.lp-cat-tile{display:flex;align-items:center;justify-content:center;padding:3px;}
/* Grid view */
.lp-results.lp-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px 10px;align-content:start;padding-top:2px;}
.lp-gitem{cursor:pointer;min-width:0;}
.lp-gthumb{width:100%;aspect-ratio:1;border-radius:10px;object-fit:cover;background:var(--bg);}
.lp-gname{font-size:12px;font-weight:500;margin-top:5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
/* No-photo grid tile: the title IS the tile, nothing repeated below */
.lp-gnoimg{display:flex;align-items:center;justify-content:center;text-align:center;padding:8px;}
.lp-gtitle{font-size:11px;font-weight:600;line-height:1.3;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;overflow-wrap:anywhere;}
.lp-chips{display:flex;gap:8px;flex-wrap:wrap;}
.lp-results{overflow-y:auto;flex:1;min-height:120px;}
.lp-item{display:flex;align-items:center;gap:12px;padding:9px 6px;border-bottom:1px solid var(--border);cursor:pointer;border-radius:8px;transition:background .12s;}
.lp-item:hover{background:var(--brand-pale);}
.lp-thumb{width:44px;height:44px;border-radius:8px;object-fit:cover;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:20px;background:var(--bg);}
.lp-item-title{font-size:13.5px;font-weight:500;}
.lp-item-price{font-size:12px;color:var(--text-muted);margin-top:1px;}
.lp-none{padding:24px;text-align:center;color:var(--text-faint);font-size:13px;}
/* Shared-listing card inside a bubble */
.bubble-listing{padding:6px 6px 8px;}
.bubble-listing .bubble-meta{margin:8px 4px 0 8px;}
.msg-listing-card{display:flex;align-items:center;gap:10px;background:rgba(0,0,0,.045);border-radius:12px;padding:8px;cursor:pointer;min-width:190px;}
.bubble.mine .msg-listing-card{background:rgba(255,255,255,.16);}
.mlc-thumb{width:52px;height:52px;border-radius:9px;object-fit:cover;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:24px;background:var(--bg);}
.mlc-title{font-size:13.5px;font-weight:600;line-height:1.3;}
.mlc-price{font-size:12px;opacity:.8;margin-top:2px;}
.msg-listing-gone{color:var(--text-faint);font-size:12.5px;cursor:default;justify-content:center;}
.chat-input{flex:1;padding:10px 16px;border:1px solid var(--border);border-radius:20px;font-size:14px;font-family:'DM Sans',sans-serif;resize:none;outline:none;min-height:40px;max-height:120px;background:var(--bg);transition:border .15s;}
.chat-input:focus{border-color:var(--brand-light);background:#fff;}
.send-btn{background:var(--brand);color:#fff;border:none;width:40px;height:40px;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:background .15s;}
.send-btn:hover{background:var(--brand-mid);}
.no-chat{flex:1;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:12px;color:var(--text-faint);font-size:14px;}
/* PROFILE */
.profile-wrap{max-width:640px;margin:0 auto;padding:var(--sp-5) var(--sp-5) var(--sp-7);}
/* Fine print under Log out — deliberately quiet, but a real tap target on mobile. */
.profile-legal{display:flex;justify-content:center;align-items:center;flex-wrap:wrap;gap:6px;margin-top:6px;font-size:12px;color:var(--text-faint);}
/* Padding, not font-size, does the work here: it grows the thumb target to ~34px without
   making the fine print look any bigger. margin-top is pulled back to compensate. */
.profile-legal a{color:var(--text-muted);text-decoration:none;padding:10px 8px;}
.profile-legal a:hover{color:var(--text);text-decoration:underline;}
/* The Log out button. Transparent rather than --bg: without the card behind it, a cream fill
   on a cream page is no fill at all, and the border is what makes it a button. */
.pf-logout{background:transparent;color:var(--text-muted);border:1px solid var(--border);margin-top:16px;}
.pf-logout:hover{border-color:var(--brand-light);color:var(--text);}
.profile-consent{text-align:center;font-size:11px;color:var(--text-faint);}
.profile-consent:empty{display:none;}
/* Key/value row in admin detail modals. The rows above it in aViewStu still carry these
   same values inline (known debt) — this is the class they should migrate to. */
.a-kv{display:flex;justify-content:space-between;padding:8px 0;font-size:13px;border-top:1px solid var(--border);}
.a-kv label{color:var(--text-muted);}
.a-consent-v{color:var(--text-faint);}
.a-consent-stale{color:var(--warning);}
.a-consent-none{color:var(--danger);font-weight:500;}
/* Edit-profile avatar preview: clicking the picture opens the file picker, which is
   what people try first. The "Change photo" button beside it stays the keyboard path. */
.ep-avatar-preview{cursor:pointer;}
.ep-avatar-preview:hover{opacity:.85;}

/* cover/center kept in CSS for the same reason as .nav-avatar above. */
.profile-avatar-lg{width:64px;height:64px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:22px;font-weight:600;color:#fff;background:var(--brand);background-size:cover;background-position:center;background-repeat:no-repeat;}
/* Profile identity, before it loads. These fields are empty in the markup (see the note
   in index.html) so no stranger's name is ever shown; :empty gives that moment a neutral
   grey block instead of a collapsed, broken-looking card. renderProfile() fills them in
   and the skeleton stops matching on its own — no JS toggle to remember. */
/* background-COLOR, never the `background` shorthand: an avatar with a photo is also
   :empty (paintAvatarEl clears the initials and paints a background-image), and the
   shorthand would reset this element's background-size:cover and no-repeat, rendering
   the photo full-size from the top-left corner of a 64px circle. */
#profileAvatar:empty{background-color:var(--border);}
#profileName:empty::before,#profileEmail:empty::before{content:"";display:block;background:var(--border);border-radius:5px;opacity:.5;}
#profileName:empty::before{width:160px;height:22px;}
#profileEmail:empty::before{width:190px;height:13px;margin-top:5px;}
.edu-badge{display:inline-flex;align-items:center;gap:5px;background:var(--brand-pale);color:var(--brand);font-size:12px;font-weight:600;padding:4px 10px;border-radius:20px;margin-top:6px;}
.stu-link{cursor:pointer;color:inherit;font-weight:inherit;}.stu-link:hover{color:var(--brand);text-decoration:underline;}
.stu-link-a{cursor:pointer;color:inherit;font-weight:inherit;}.stu-link-a:hover{color:var(--brand);text-decoration:underline;}
.hist-stat-grid{display:grid;grid-template-columns:repeat(5,1fr);border-top:1px solid var(--border);}
.hist-stat{padding:14px 10px;text-align:center;border-right:1px solid var(--border);}
.hist-stat:last-child{border-right:none;}
.hist-stat-num{font-family:'DM Serif Display',serif;font-size:28px;color:var(--brand);}
.hist-stat-label{font-size:11px;color:var(--text-muted);margin-top:3px;text-transform:uppercase;letter-spacing:.04em;}
@media(max-width:600px){.hist-stat-grid{grid-template-columns:repeat(3,1fr);}}
.susp-timeline{display:flex;flex-direction:column;gap:14px;}
.susp-entry{display:flex;align-items:flex-start;gap:12px;}
.susp-dot{width:10px;height:10px;border-radius:50%;flex-shrink:0;margin-top:4px;}
.htab{background:none;border:none;border-bottom:2px solid transparent;padding:10px 18px;font-size:13px;font-weight:500;color:var(--text-muted);cursor:pointer;font-family:'DM Sans',sans-serif;transition:all .15s;white-space:nowrap;}
.htab.active{color:var(--brand);border-bottom-color:var(--brand);}
.htab:hover:not(.active){color:var(--text);}
.htab-pane{display:none;}
.htab-pane.active{display:block;}
.hist-empty{padding:28px;text-align:center;font-size:13px;color:var(--text-faint);}
.hist-row{display:flex;align-items:flex-start;gap:14px;padding:14px 20px;border-bottom:1px solid var(--border);cursor:pointer;transition:background .12s;}
.hist-row:hover{background:var(--bg);}
.hist-row:last-child{border-bottom:none;}
.hist-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:10px;padding:16px;}
.hist-card{background:var(--bg);border:1.5px solid var(--border);border-radius:var(--radius-sm);padding:16px;cursor:pointer;transition:border-color .15s,box-shadow .15s;}
.hist-card:hover{border-color:var(--brand);box-shadow:0 2px 10px rgba(59,91,165,.1);}
.view-toggle{display:flex;gap:2px;background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:3px;}
.view-btn{background:none;border:none;padding:5px 10px;border-radius:5px;cursor:pointer;color:var(--text-muted);transition:all .15s;font-size:14px;line-height:1;}
.view-btn.active{background:var(--surface);color:var(--brand);box-shadow:0 1px 3px rgba(0,0,0,.1);}
.hdrawer-overlay{position:fixed;inset:0;background:rgba(0,0,0,.38);z-index:500;opacity:0;pointer-events:none;transition:opacity .2s;}
.hdrawer-overlay.open{opacity:1;pointer-events:all;}
.hdrawer{position:fixed;top:0;right:0;height:100vh;width:480px;max-width:96vw;background:var(--surface);box-shadow:-4px 0 32px rgba(0,0,0,.14);z-index:501;transform:translateX(100%);transition:transform .27s cubic-bezier(.4,0,.2,1);overflow-y:auto;display:flex;flex-direction:column;}
.hdrawer.open{transform:translateX(0);}
.hdrawer-hdr{padding:18px 24px;border-bottom:1px solid var(--border);display:flex;align-items:flex-start;justify-content:space-between;gap:12px;position:sticky;top:0;background:var(--surface);z-index:2;}
.hdrawer-body{padding:22px 24px;flex:1;}
.listing-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:3px;margin-top:4px;}
.lg-cell{position:relative;aspect-ratio:1/1;background:var(--bg);border-radius:6px;overflow:hidden;cursor:pointer;display:flex;flex-direction:column;transition:opacity .15s;}
.lg-cell:hover{opacity:.82;}
.lg-fill{flex:1;display:flex;align-items:flex-start;padding:9px 9px 2px;overflow:hidden;}
.lg-fill-title{font-family:'DM Serif Display',serif;font-size:13px;line-height:1.16;display:-webkit-box;-webkit-line-clamp:4;line-clamp:4;-webkit-box-orient:vertical;overflow:hidden;}
.lg-cap{padding:5px 7px;background:var(--surface);}
.lg-cap-title{font-size:10px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--text);}
.lg-cap-rent{font-size:10px;color:var(--brand);font-weight:600;}
.lg-badge{position:absolute;top:5px;right:5px;font-size:9px;font-weight:700;padding:2px 5px;border-radius:8px;letter-spacing:.02em;}
.section-divider{height:1px;background:var(--border);margin:20px 0;}
/* HOW IT WORKS */
.hiw-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:24px;margin-top:36px;}
.hiw-card{background:var(--surface);border-radius:var(--radius-lg);border:1px solid var(--border);padding:28px 26px 30px;box-shadow:var(--shadow-sm);}
.hiw-icon{width:46px;height:46px;background:var(--brand-pale);color:var(--brand);border-radius:14px;display:flex;align-items:center;justify-content:center;margin-bottom:16px;}
/* SCHOOL PICKER DROPDOWN */
.school-option{padding:10px 14px;cursor:pointer;font-size:14px;color:var(--text);border-bottom:1px solid var(--border);}
.school-option:last-child{border-bottom:none;}
.school-option:hover{background:var(--bg);}
/* HOME SECTIONS */
.home-section{padding:var(--sp-7) var(--sp-5);background:var(--bg);}
.home-section-inner{max-width:900px;margin:0 auto;}
.home-h2{font-family:'DM Serif Display',serif;font-size:clamp(28px,4vw,38px);line-height:1.15;text-align:center;margin-bottom:var(--sp-3);}
.home-lead{text-align:center;color:var(--text-muted);font-size:16px;max-width:600px;margin:0 auto;line-height:1.7;}
.feat-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:20px;margin-top:36px;}
.feat-card{background:var(--surface);border-radius:var(--radius-lg);border:1px solid var(--border);padding:26px;box-shadow:var(--shadow-sm);}
.feat-icon{width:46px;height:46px;border-radius:14px;background:var(--brand-pale);color:var(--brand);display:flex;align-items:center;justify-content:center;margin-bottom:14px;}
.feat-name{font-family:'DM Serif Display',serif;font-size:var(--fs-heading);margin-bottom:6px;}
.feat-desc{font-size:14px;color:var(--text-muted);line-height:1.6;}
.who-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:24px;margin-top:36px;}
.who-card{background:var(--surface);border-radius:var(--radius-lg);border:1px solid var(--border);padding:28px;text-align:center;box-shadow:var(--shadow-sm);}
.who-icon{width:52px;height:52px;border-radius:16px;background:var(--brand-pale);color:var(--brand);display:flex;align-items:center;justify-content:center;margin:0 auto 14px;}
.who-name{font-family:'DM Serif Display',serif;font-size:20px;margin-bottom:10px;}
.who-desc{font-size:14px;color:var(--text-muted);line-height:1.7;margin:0;}
.school-list{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:10px;}
.school-list li{font-size:15px;color:var(--text-muted);display:flex;align-items:flex-start;gap:10px;}
.school-list li svg{flex:none;margin-top:4px;color:var(--brand);}
/* The green dot in the hero badge — a state, so it is drawn, not written. */
.live-dot{width:7px;height:7px;border-radius:50%;background:var(--brand-light);flex:none;}
.site-footer{background:var(--brand);padding:40px 24px;}
.footer-inner{max-width:900px;margin:0 auto;display:flex;flex-direction:column;align-items:center;gap:16px;text-align:center;}
.footer-links{display:flex;gap:24px;flex-wrap:wrap;justify-content:center;}
.footer-links a{color:rgba(255,255,255,0.65);font-size:14px;text-decoration:none;}
.footer-links a:hover{color:#fff;}
.footer-copy{color:rgba(255,255,255,0.6);font-size:13px;margin:0;}
/* Two classes so it outranks any bare .s-logo rule wherever it sits in the file, including
   the phone header's resize. The footer wordmark is smaller and is not a link. */
.site-footer .s-logo{font-size:18px;cursor:default;}

/* ============================================================
   ADMIN INTERFACE
============================================================ */
#adminApp{display:none;min-height:100vh;}
.a-layout{display:flex;min-height:100vh;}
.a-sidebar{width:240px;background:var(--brand);display:flex;flex-direction:column;position:fixed;top:0;left:0;height:100vh;z-index:50;overflow-y:auto;transition:width .2s ease;overflow-x:hidden;}
.a-logo{padding:20px 20px 16px;border-bottom:1px solid rgba(255,255,255,0.1);}
.a-logo-text{font-family:'DM Serif Display',serif;color:#fff;font-size:20px;}
.a-logo-text span{color:var(--accent);}
.a-logo-sub{font-size:10px;color:rgba(255,255,255,0.6);margin-top:2px;letter-spacing:.08em;text-transform:uppercase;}
.a-sec-label{padding:10px 10px 3px;font-size:10px;font-weight:600;color:rgba(255,255,255,0.6);text-transform:uppercase;letter-spacing:.08em;}
.a-nav{flex:1;padding:8px 10px;display:flex;flex-direction:column;gap:1px;}
.a-nav-item{display:flex;align-items:center;gap:10px;padding:9px 12px;border-radius:var(--radius-sm);cursor:pointer;color:rgba(255,255,255,0.65);font-size:13px;font-weight:500;transition:all .15s;border:none;background:none;font-family:'DM Sans',sans-serif;width:100%;text-align:left;}
.a-nav-item:hover{background:rgba(255,255,255,0.08);color:#fff;}
.a-nav-item.active{background:rgba(255,255,255,0.16);color:#fff;}
.n-icon{width:20px;height:20px;flex:none;display:flex;align-items:center;justify-content:center;}
.n-icon svg{display:block;}
/* Inline icon: rides the text baseline inside a button, chip or heading. */
.ico{display:inline-block;vertical-align:-2px;flex:none;}
.n-badge{background:var(--accent);color:var(--brand);font-size:10px;font-weight:700;padding:1px 7px;border-radius:10px;margin-left:auto;}
.n-badge.red{background:#e74c3c;color:#fff;}
.a-sidebar-footer{padding:12px 10px;border-top:1px solid rgba(255,255,255,0.1);}
.a-admin-av{width:32px;height:32px;border-radius:50%;background:var(--accent);color:var(--brand);display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;flex-shrink:0;}
.btn-logout-a{width:100%;margin-top:6px;padding:8px;background:rgba(255,255,255,0.07);border:none;border-radius:var(--radius-sm);color:rgba(255,255,255,0.55);font-size:12px;cursor:pointer;font-family:'DM Sans',sans-serif;transition:all .15s;}
.btn-logout-a:hover{background:rgba(255,255,255,0.13);color:#fff;}
.a-main{margin-left:240px;flex:1;min-height:100vh;transition:margin-left .2s ease;}
/* The icon-strip collapse is a DESKTOP idea, so every rule for it lives inside this query. On a
   phone the sidebar is a drawer and always shows the full, labelled menu, even when the strip was
   chosen on a laptop. The main area's margin follows the sidebar here too — it used to be set
   inline from JS, which would have beaten the phone rule that takes it away. */
@media(min-width:769px){
/* Admin sidebar collapsed (icon-only strip) */
.a-sidebar.a-collapsed{width:52px;}
.a-sidebar.a-collapsed .a-logo{justify-content:center;padding:14px 0;}
.a-sidebar.a-collapsed .a-logo-text,.a-sidebar.a-collapsed .a-logo-sub,.a-sidebar.a-collapsed #adminSchoolCtx{display:none;}
.a-sidebar.a-collapsed .a-sec-label{display:none;}
.a-sidebar.a-collapsed .n-label{display:none;}
.a-sidebar.a-collapsed .a-nav-item{justify-content:center;padding:10px 0;position:relative;overflow:visible;}
.a-sidebar.a-collapsed .n-badge{position:absolute;top:5px;right:5px;width:8px;height:8px;min-width:0;padding:0;font-size:0;border-radius:50%;}
.a-sidebar.a-collapsed .a-admin-info{display:none;}
.a-sidebar.a-collapsed .btn-logout-a{display:none;}
.a-sidebar.a-collapsed .a-sidebar-footer{padding:10px 0;display:flex;justify-content:center;border-top:1px solid rgba(255,255,255,0.1);}
/* Tooltips — only visible in collapsed mode */
.a-sidebar.a-collapsed .a-nav-item::after{content:attr(data-tooltip);position:absolute;left:54px;top:50%;transform:translateY(-50%);background:#1a1a2e;color:#fff;padding:5px 10px;border-radius:6px;font-size:12px;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity .15s;z-index:200;border:1px solid rgba(255,255,255,0.12);box-shadow:0 2px 8px rgba(0,0,0,0.2);}
.a-sidebar.a-collapsed .a-nav-item:hover::after,.a-sidebar.a-collapsed .a-nav-item:focus::after{opacity:1;}
.a-sidebar.a-collapsed + .a-main{margin-left:52px;}
}
/* Admin sidebar toggle button */
.a-sidebar-toggle{background:none;border:none;color:rgba(255,255,255,0.45);cursor:pointer;font-size:17px;padding:3px 7px;border-radius:4px;transition:all .15s;font-family:'DM Sans',sans-serif;line-height:1;flex-shrink:0;}
.a-sidebar-toggle:hover{background:rgba(255,255,255,0.1);color:#fff;}
.a-topbar{background:var(--surface);border-bottom:1px solid var(--border);padding:0 24px;height:54px;display:flex;align-items:center;justify-content:space-between;position:sticky;top:0;z-index:40;}
.a-topbar-title{font-size:16px;font-weight:600;}
.a-topbar-right{display:flex;align-items:center;gap:8px;}
.a-content{padding:24px;}
.a-topbar-left{display:flex;align-items:center;gap:6px;min-width:0;}
.a-menu-btn{display:none;}
/* Shown only by the hidden attribute being removed (closeAdminDrawer puts it back). Between the
   page (sidebar 50, sticky top bar 40) and every pop-up (500). */
.a-drawer-backdrop{position:fixed;inset:0;z-index:60;background:rgba(20,18,15,.42);}

/* Broadcast: compose beside a 340px preview on a wide screen. It was an inline grid with
   min-width:680px, so anything narrower was pushed sideways, and between 769px and about 1000px
   the compose column was squeezed to around 150px. Below 1024px it stacks instead. */
.a-bc-layout{display:grid;grid-template-columns:1fr 340px;gap:18px;align-items:start;}
@media(max-width:1023px){.a-bc-layout{grid-template-columns:1fr;}}
/* A two-up row of fields that stacks on a phone. Its column split lived in an inline style,
   which no phone rule can override, so it lives here now. */
.a-2col{display:grid;grid-template-columns:1fr 1fr;gap:12px;}

/* ---------------------------------------------------------------- ADMIN ON A PHONE
   Until this block the admin had no phone layout at all: a 240px sidebar fixed to the left edge
   left about 150px of a 390px screen for everything else. Below 769px the sidebar slides in as a
   drawer from a menu button, and the content takes the full width. */
@media(max-width:768px){
  .a-sidebar{width:280px;max-width:85vw;height:100vh;height:100dvh;z-index:70;
    transform:translateX(-100%);transition:transform var(--dur-slow) var(--ease);}
  .a-sidebar.is-open{transform:none;box-shadow:var(--shadow-lg);}
  /* The strip toggle is meaningless when the whole sidebar is already a drawer. */
  .a-sidebar-toggle{display:none;}
  .a-main{margin-left:0;}
  .a-menu-btn{display:inline-flex;align-items:center;justify-content:center;width:40px;height:40px;
    margin-left:-8px;border:0;border-radius:10px;background:none;color:var(--text);cursor:pointer;}
  .a-menu-btn:focus-visible{outline:var(--focus-ring);outline-offset:0;}
  .a-topbar{padding:0 12px;}
  .a-topbar-title{font-size:15px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
  /* "Live data" is a label, and Student view is in the drawer's footer — neither earns top-bar
     width on a phone. Refresh and NestBot stay. */
  .a-top-note,.a-top-student{display:none;}
  .a-content{padding:16px;}
  .a-2col{grid-template-columns:1fr;}
  /* Tables scroll inside their own box instead of widening the page. Six of the admin's eight
     tables had no scroll wrapper, so on a phone each one dragged the whole screen sideways. As a
     block the table keeps its rows and columns; only the overflow is contained. It outranks the
     global table{width:100%} on specificity, wherever that sits. */
  .a-content table{display:block;overflow-x:auto;max-width:100%;}
}
.a-section{display:none;}.a-section.active{display:block;animation:fi .2s ease;}
@keyframes fi{from{opacity:0;transform:translateY(6px);}to{opacity:1;transform:none;}}

/* ADMIN STAT CARDS */
.a-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px;margin-bottom:22px;}
.asc{background:var(--surface);border-radius:var(--radius);border:1px solid var(--border);padding:16px 18px;}
.asc-label{font-size:10px;font-weight:600;color:var(--text-faint);text-transform:uppercase;letter-spacing:.06em;margin-bottom:6px;}
.asc-num{font-family:'DM Serif Display',serif;font-size:28px;color:var(--brand);}
.asc-sub{font-size:11px;color:var(--text-faint);margin-top:3px;}
.asc-warn .asc-num{color:var(--warning);}.asc-danger .asc-num{color:var(--danger);}
.asc-success .asc-num{color:var(--success);}.asc-pin .asc-num{color:var(--pin);}
.asc-click{cursor:pointer;transition:border-color .15s,box-shadow .15s;}
.asc-click:hover{border-color:var(--brand);box-shadow:0 2px 10px rgba(59,91,165,.08);}
.asc-view{font-size:10px;color:var(--brand);font-weight:600;letter-spacing:.03em;margin-top:6px;opacity:0;transition:opacity .15s;}
.asc-click:hover .asc-view{opacity:1;}
.filter-chip{background:var(--surface);border:1px solid var(--border);border-radius:20px;padding:4px 13px;font-size:12px;font-weight:500;cursor:pointer;color:var(--text-muted);font-family:inherit;transition:all .15s;white-space:nowrap;}
.filter-chip.active{background:var(--brand);border-color:var(--brand);color:#fff;}
.filter-chip:hover:not(.active){border-color:var(--brand);color:var(--brand);}
.filter-row-label{font-size:10px;font-weight:700;color:var(--text-faint);width:54px;flex-shrink:0;letter-spacing:.06em;align-self:center;}
.school-cell-link{cursor:pointer;color:var(--brand);font-weight:600;text-decoration:none;transition:opacity .15s;}
.school-cell-link:hover{opacity:.75;text-decoration:underline;}

/* ADMIN TABLE */
.tcard{background:var(--surface);border-radius:var(--radius);border:1px solid var(--border);overflow:hidden;margin-bottom:18px;}
.tcard-head{padding:13px 18px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;}
.tcard-title{font-size:15px;font-weight:600;}
.a-sbar{padding:7px 14px;border:1px solid var(--border);border-radius:20px;font-size:13px;background:var(--bg);outline:none;font-family:'DM Sans',sans-serif;width:190px;}
table{width:100%;border-collapse:collapse;}
th{font-size:10px;font-weight:600;color:var(--text-faint);text-transform:uppercase;letter-spacing:.06em;padding:9px 14px;text-align:left;background:var(--bg);border-bottom:1px solid var(--border);}
td{padding:10px 14px;border-bottom:1px solid var(--border);font-size:13px;vertical-align:middle;}
tr:last-child td{border-bottom:none;}tr:hover td{background:#fafbfa;}
.pill{display:inline-flex;align-items:center;font-size:10px;font-weight:600;padding:3px 9px;border-radius:20px;}
.pill-pending{background:var(--warning-pale);color:var(--warning);}
.pill-approved{background:var(--success-pale);color:var(--success);}
.pill-rejected{background:var(--danger-pale);color:var(--danger);}
.pill-active{background:var(--brand-pale);color:var(--brand);}
.pill-pinned{background:var(--pin-pale);color:var(--pin);}
.pill-suspended{background:#f3e8ff;color:#6b21a8;}
.arow{display:flex;gap:5px;flex-wrap:wrap;}
.btn-sm-a{padding:6px 14px;border-radius:var(--radius-sm);font-size:12px;font-weight:600;cursor:pointer;border:none;font-family:'DM Sans',sans-serif;transition:all .15s;}
.btn-a-danger{background:var(--danger-pale);color:var(--danger);}.btn-a-danger:hover{background:var(--danger);color:#fff;}
.btn-a-success{background:var(--success-pale);color:var(--success);}.btn-a-success:hover{background:var(--success);color:#fff;}
.btn-a-neutral{background:var(--bg);color:var(--text-muted);border:1px solid var(--border);}.btn-a-neutral.active{background:var(--brand);color:#fff;border-color:var(--brand);}
.btn-a-pin{background:var(--pin-pale);color:var(--pin);}.btn-a-pin:hover{background:var(--pin);color:#fff;}
.btn-a-brand{background:var(--brand);color:#fff;}.btn-a-brand:hover{background:var(--brand-mid);}

/* APPROVAL CARDS */
.acard{background:var(--surface);border-radius:var(--radius);border:1px solid var(--border);padding:18px;margin-bottom:12px;}
.acard:hover{box-shadow:0 4px 14px rgba(0,0,0,0.07);}
.acard-title{font-size:15px;font-weight:600;margin-bottom:3px;}
.acard-meta{font-size:12px;color:var(--text-muted);}
.acard-desc{font-size:13px;color:var(--text-muted);line-height:1.6;margin:10px 0 12px;padding:10px;background:var(--bg);border-radius:var(--radius-sm);}
.acard-tags{display:flex;gap:5px;flex-wrap:wrap;margin-bottom:12px;}
.atag{background:var(--bg);border:1px solid var(--border);color:var(--text-muted);font-size:11px;padding:2px 8px;border-radius:4px;}
.mav{width:28px;height:28px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;color:#fff;flex-shrink:0;}

/* EDITOR */
.ed-layout{display:grid;grid-template-columns:310px 1fr;gap:18px;align-items:start;}
.ed-panel{background:var(--surface);border-radius:var(--radius);border:1px solid var(--border);overflow:hidden;}
.ed-tabs{display:flex;border-bottom:1px solid var(--border);}
.ed-tab{flex:1;padding:10px;text-align:center;font-size:12px;font-weight:600;cursor:pointer;border:none;background:none;color:var(--text-muted);font-family:'DM Sans',sans-serif;transition:all .15s;border-bottom:2px solid transparent;}
.ed-tab.active{color:var(--brand);border-bottom:2px solid var(--brand);background:var(--brand-pale);}
.ed-body{padding:16px;}
.es-title{font-size:10px;font-weight:700;color:var(--text-faint);text-transform:uppercase;letter-spacing:.07em;margin:14px 0 8px;}
.es-title:first-child{margin-top:0;}
.c-row{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;}
.c-label{font-size:13px;color:var(--text-muted);}
.c-swatch{width:30px;height:30px;border-radius:7px;border:2px solid var(--border);overflow:hidden;flex-shrink:0;}
.c-swatch input[type=color]{width:46px;height:46px;border:none;outline:none;cursor:pointer;transform:translate(-8px,-8px);}
.preset-row{display:flex;gap:7px;flex-wrap:wrap;margin-bottom:4px;}
.pdot{width:24px;height:24px;border-radius:50%;cursor:pointer;border:2px solid transparent;transition:all .15s;}
.pdot:hover{border-color:var(--text);transform:scale(1.12);}
.ced-item{margin-bottom:12px;}
.ced-label{font-size:10px;font-weight:600;color:var(--text-faint);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px;}
.ced-input{width:100%;padding:8px 10px;border:1px solid var(--border);border-radius:6px;font-size:13px;font-family:'DM Sans',sans-serif;background:var(--bg);outline:none;}
.ced-input:focus{border-color:var(--brand-light);background:#fff;}
.apply-btn-e{width:100%;margin-top:12px;padding:10px;background:var(--brand);color:#fff;border:none;border-radius:var(--radius-sm);font-size:14px;font-weight:600;cursor:pointer;font-family:'DM Sans',sans-serif;}
.apply-btn-e:hover{background:var(--brand-mid);}
.reset-btn-e{width:100%;margin-top:7px;padding:9px;background:var(--bg);color:var(--text-muted);border:1px solid var(--border);border-radius:var(--radius-sm);font-size:12px;cursor:pointer;font-family:'DM Sans',sans-serif;}
/* PREVIEW FRAME */
.pv-frame{background:var(--surface);border-radius:var(--radius);border:1px solid var(--border);overflow:hidden;}
.pv-nav{background:var(--brand);padding:0 14px;height:42px;display:flex;align-items:center;justify-content:space-between;}
.pv-logo{font-family:'DM Serif Display',serif;color:#fff;font-size:17px;}
.pv-logo em{color:var(--accent);font-style:normal;}
.pv-hero-d{background:var(--brand);padding:28px 18px;text-align:center;}
.pv-hero-d h1{font-family:'DM Serif Display',serif;color:#fff;font-size:24px;line-height:1.2;margin-bottom:8px;}
.pv-hero-d h1 em{color:var(--accent);font-style:normal;}
.pv-hero-d p{color:rgba(255,255,255,0.7);font-size:12px;max-width:300px;margin:0 auto 14px;}
.pv-cta-d{display:inline-block;background:var(--accent);color:var(--brand);padding:8px 18px;border-radius:6px;font-size:12px;font-weight:600;}
.pv-body{padding:16px;}
.pv-cards{display:grid;grid-template-columns:1fr 1fr;gap:8px;}
.pv-card{background:var(--surface);border:1px solid var(--border);border-radius:9px;overflow:hidden;}
.pv-card-img{height:58px;background:var(--brand-pale);display:flex;align-items:center;justify-content:center;font-size:20px;}
.pv-card-body{padding:8px;}
.pv-card-title{font-size:11px;font-weight:600;margin-bottom:2px;}
.pv-card-price{font-family:'DM Serif Display',serif;font-size:14px;color:var(--brand);}
/* CHARTS */
.charts-row{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:20px;}
.chart-card{background:var(--surface);border-radius:var(--radius);border:1px solid var(--border);padding:16px;}
.chart-title{font-size:10px;font-weight:700;color:var(--text-faint);text-transform:uppercase;letter-spacing:.07em;margin-bottom:12px;}
.bar-chart{display:flex;align-items:flex-end;gap:6px;height:80px;}
.bwrap{flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;}
.bar{width:100%;background:var(--brand-pale);border-radius:4px 4px 0 0;cursor:pointer;transition:background .2s;}
.bar:hover{background:var(--brand-light);}
.blabel{font-size:10px;color:var(--text-faint);}.bval{font-size:10px;font-weight:600;color:var(--text-muted);}
/* ACTIVITY LOG */
/* cursor:pointer lives here now — every activity row is clickable (opens its detail drawer),
   so it was being repeated inline on each one. */
.al-item{display:flex;align-items:flex-start;gap:10px;padding:10px 0;border-bottom:1px solid var(--border);cursor:pointer;}
.al-item:last-child{border-bottom:none;}
.al-dot{width:7px;height:7px;border-radius:50%;flex-shrink:0;margin-top:4px;}
.al-text{font-size:13px;flex:1;line-height:1.4;}
.al-time{font-size:11px;color:var(--text-faint);flex-shrink:0;}
/* SETTINGS TOGGLES */
.tog-wrap{position:relative;width:40px;height:22px;cursor:pointer;flex-shrink:0;}
.tog-track{position:absolute;inset:0;border-radius:11px;transition:background .2s;}
.tog-thumb{position:absolute;top:3px;width:16px;height:16px;background:#fff;border-radius:50%;transition:left .2s;box-shadow:0 1px 3px rgba(0,0,0,0.3);}
/* BROADCAST */
.msg-type-btn{padding:7px 14px;border-radius:20px;border:1px solid var(--border);background:var(--surface);font-size:12px;font-weight:500;color:var(--text-muted);cursor:pointer;transition:all .15s;}
.msg-type-btn:hover{border-color:var(--brand-light);color:var(--brand);}
.active-type{background:var(--brand-pale);border-color:var(--brand-light);color:var(--brand);font-weight:600;}
.tmpl-btn{padding:9px 12px;border-radius:var(--radius-sm);border:1px solid var(--border);background:var(--bg);font-size:12px;color:var(--text-muted);cursor:pointer;text-align:left;font-family:'DM Sans',sans-serif;transition:all .15s;width:100%;}
.tmpl-btn:hover{background:var(--brand-pale);border-color:var(--brand-light);color:var(--brand);}
/* HEALTH */
/* MAJOR CHART */
.maj-row{display:flex;align-items:center;gap:8px;margin-bottom:6px;}
.maj-bar-outer{flex:1;background:var(--bg);border-radius:20px;height:8px;overflow:hidden;}
.maj-bar-inner{height:100%;background:var(--brand-light);border-radius:20px;}
.maj-label{font-size:12px;color:var(--text-muted);min-width:90px;}
.maj-count{font-size:12px;font-weight:600;color:var(--brand);min-width:20px;text-align:right;}

/* ============================================================
   AI CHATBOT (admin)
============================================================ */
.ai-fab{position:fixed;bottom:26px;right:26px;width:50px;height:50px;border-radius:50%;background:var(--brand);color:#fff;border:none;cursor:pointer;font-size:20px;display:flex;align-items:center;justify-content:center;box-shadow:0 6px 20px rgba(26,58,42,0.4);z-index:300;transition:all .2s;}
.ai-fab:hover{transform:scale(1.08);background:var(--brand-mid);}
.ai-fab-badge{position:absolute;top:-1px;right:-1px;width:16px;height:16px;background:var(--accent);border-radius:50%;font-size:9px;font-weight:700;color:var(--brand);display:flex;align-items:center;justify-content:center;}
.ai-chat{position:fixed;bottom:86px;right:26px;width:350px;height:490px;background:var(--surface);border-radius:var(--radius);border:1px solid var(--border);box-shadow:0 16px 48px rgba(0,0,0,0.18);z-index:300;display:none;flex-direction:column;overflow:hidden;}
/* Directly after the rule it narrows, on purpose: the admin's phone block sits earlier in this
   file, and a same-weight override there would lose to this rule on source order. */
@media(max-width:768px){.ai-chat{left:12px;right:12px;width:auto;bottom:84px;height:min(490px,calc(100dvh - 140px));}}
.ai-chat.open{display:flex;}
.ai-chat-head{background:var(--brand);padding:13px 15px;display:flex;align-items:center;gap:10px;}
.ai-chat-av{width:32px;height:32px;border-radius:50%;background:var(--accent);display:flex;align-items:center;justify-content:center;font-size:15px;color:var(--brand);}
.ai-chat-name{color:#fff;font-weight:600;font-size:14px;}
.ai-chat-status{color:rgba(255,255,255,0.55);font-size:11px;}
.ai-close-btn{margin-left:auto;background:transparent;border:none;color:rgba(255,255,255,0.6);font-size:18px;cursor:pointer;}
.ai-msgs{flex:1;overflow-y:auto;padding:12px;display:flex;flex-direction:column;gap:9px;}
.ai-msg-b{max-width:84%;padding:9px 12px;border-radius:11px;font-size:13px;line-height:1.5;}
.ai-msg-b.bot{background:var(--bg);border-radius:4px 11px 11px 11px;color:var(--text);}
.ai-msg-b.user{background:var(--brand);color:#fff;border-radius:11px 4px 11px 11px;align-self:flex-end;}
.ai-msg-b.thinking{color:var(--text-faint);font-style:italic;}
.ai-qbtns{display:flex;gap:5px;flex-wrap:wrap;padding:0 12px 9px;}
.ai-qbtn{padding:4px 10px;border-radius:20px;border:1px solid var(--border);background:var(--surface);font-size:11px;color:var(--text-muted);cursor:pointer;font-family:'DM Sans',sans-serif;transition:all .15s;}
.ai-qbtn:hover{background:var(--brand-pale);border-color:var(--brand-light);color:var(--brand);}
.ai-input-row{padding:10px 12px;border-top:1px solid var(--border);display:flex;gap:8px;}
.ai-input{flex:1;padding:8px 12px;border:1px solid var(--border);border-radius:20px;font-size:13px;font-family:'DM Sans',sans-serif;outline:none;background:var(--bg);}
.ai-input:focus{border-color:var(--brand-light);background:#fff;}
.ai-send{background:var(--brand);color:#fff;border:none;width:32px;height:32px;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:13px;flex-shrink:0;}
.ai-send:hover{background:var(--brand-mid);}

/* SHARED TOAST */
.toast{position:fixed;bottom:22px;left:50%;transform:translateX(-50%) translateY(20px);background:var(--brand);color:#fff;padding:10px 20px;border-radius:10px;font-size:13px;font-weight:500;z-index:9999;transition:all .3s;pointer-events:none;opacity:0;}
/* Tappable new-message banner (drops from the top, phone-notification style) */
.msg-toast{position:fixed;top:calc(14px + env(safe-area-inset-top));left:50%;transform:translateX(-50%) translateY(-16px);background:var(--surface);color:var(--text);border:1px solid var(--border);border-radius:12px;box-shadow:0 6px 24px rgba(0,0,0,.14);padding:10px 16px;z-index:9999;min-width:220px;max-width:min(360px,calc(100vw - 32px));cursor:pointer;opacity:0;transition:all .25s ease;}
.msg-toast.show{opacity:1;transform:translateX(-50%) translateY(0);}
.msg-toast-name{font-weight:600;font-size:13px;color:var(--brand);margin-bottom:2px;}
.msg-toast-preview{font-size:12.5px;color:var(--text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.toast.show{transform:translateX(-50%) translateY(0);opacity:1;}

/* DETAIL LISTING MODAL */
.detail-top{padding:14px 16px;}
.detail-poster{display:flex;align-items:center;gap:13px;}
.detail-poster-name{display:flex;align-items:center;gap:6px;font-weight:600;font-size:16px;line-height:1.2;}
.detail-poster-sub{font-size:12.5px;color:var(--text-muted);margin-top:2px;display:flex;align-items:center;gap:6px;flex-wrap:wrap;}
.detail-poster-since{font-size:11.5px;color:var(--text-faint);margin-top:2px;}
.detail-noimg{padding:30px 26px 34px;}
.detail-noimg-cat{font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;opacity:.7;margin-bottom:12px;}
.detail-noimg-title{font-family:'DM Serif Display',serif;font-size:33px;line-height:1.12;}
.detail-body{padding:24px;}
.detail-title{font-family:'DM Serif Display',serif;font-size:24px;margin-bottom:6px;}
.detail-price{font-family:'DM Serif Display',serif;font-size:32px;color:var(--brand);line-height:1;}
.detail-price span{font-family:'DM Sans',sans-serif;font-size:15px;color:var(--text-muted);font-weight:400;}
.detail-tags{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0;}
.detail-tag{background:var(--bg);color:var(--text-muted);font-size:13px;padding:5px 12px;border-radius:6px;border:1px solid var(--border);}

/* SWITCH INTERFACE BTN */
.switch-interface{background:rgba(255,255,255,0.12);border:1px solid rgba(255,255,255,0.2);color:rgba(255,255,255,0.8);font-size:11px;padding:5px 12px;border-radius:20px;cursor:pointer;font-family:'DM Sans',sans-serif;font-weight:500;transition:all .15s;}
.switch-interface:hover{background:rgba(255,255,255,0.2);color:#fff;}

/* BOOKS: course autocomplete dropdown */
.course-ac-list{position:absolute;top:calc(100% + 4px);left:0;right:0;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);max-height:262px;overflow-y:auto;z-index:300;box-shadow:0 8px 24px rgba(0,0,0,.12);}
.course-ac-item{padding:9px 12px;font-size:13px;cursor:pointer;display:flex;gap:8px;align-items:baseline;min-width:0;}
.course-ac-item:hover,.course-ac-item.ac-active{background:var(--bg);}
.course-ac-code{font-weight:600;white-space:nowrap;flex-shrink:0;}
.course-ac-name{color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.course-ac-none{padding:10px 12px;font-size:12px;color:var(--text-faint);}
.course-ac-notlisted{border-top:1px solid var(--border);color:var(--text-muted);font-style:italic;}

/* ============================================================
   MOBILE APP CHROME (≤768px only)
   Everything below is invisible on desktop: the bars are
   display:none by default and only switched on inside the
   media query, so desktop styles above are never affected.
============================================================ */
:root{
  --m-topbar-h:56px;   /* fixed mobile top bar (added in a later step) */
  --m-tabbar-h:56px;   /* fixed bottom tab bar, excluding the iPhone home-indicator inset */
}
.m-tabbar{display:none;}
/* Mobile-only brand text ("CaldwellNest") — swapped in for the desktop logo ≤768px */

@media(max-width:768px){
  /* TOP BAR — the existing .s-nav becomes a fixed 56px app bar */
  .s-nav{
    position:fixed;top:0;left:0;right:0;z-index:110;
    height:calc(var(--m-topbar-h) + env(safe-area-inset-top));
    padding:env(safe-area-inset-top) 16px 0;
    transition:transform .25s ease;
  }
  /* Hide-on-scroll: slides up out of view while scrolling down, back on scroll-up */
  .s-nav.m-hidden{transform:translateY(-100%);}
  /* .s-nav left the normal flow (fixed), so push the app content down to start below it */
  #studentApp{padding-top:calc(var(--m-topbar-h) + env(safe-area-inset-top));}
  /* One wordmark, resized — not a second copy swapped in. The phone used to show a separate
     .s-logo-m element, and the two drifted: desktop said "Nestrel", the phone still said
     "CaldwellNest". The bare .s-logo{display:none} that did the swap also hid the FOOTER
     wordmark on every phone. 20px is what the phone copy already used. */
  .s-logo{font-size:20px;}
  /* THE PHONE BAR IS CREAM (option C). Desktop and the admin sidebar keep the green, so the
     brand stays on screen somewhere. Every rule is scoped under .s-nav on purpose: .s-logo
     span is also the footer wordmark, which sits on a dark band and must keep its yellow.

     Each line below exists because a white-on-green rule goes invisible on cream:
     the bells were 80% white, "Log in" was 80% white, "Admin" was a translucent white pill,
     and Sign up's yellow fill is 1.95:1 against cream — its shape would vanish. */
  .s-nav{background:var(--bg);border-bottom:1px solid var(--border);}
  .s-nav .s-logo{color:var(--brand);}
  .s-nav .s-logo span{color:var(--accent-ink);}
  .s-nav .nav-bell{color:#4a423b;}
  .s-nav .nav-bell:hover{color:var(--text);}
  .s-nav .nav-ghost{color:#4a423b;}
  .s-nav .nav-ghost:hover{background:var(--bg-alt);color:var(--text);}
  .s-nav .nav-solid{background:var(--brand);color:#fff;}
  .s-nav .nav-solid:hover{background:var(--brand-mid);}
  .s-nav .switch-interface{background:transparent;border-color:var(--border);color:#4a423b;}
  .s-nav .switch-interface:hover{background:var(--bg-alt);color:var(--text);}
  /* Browse & Messages moved into the bottom tab bar */
  #navUser .nav-btn{display:none;}
  /* BOTTOM TAB BAR — always visible, never hides on scroll */
  .m-tabbar{
    display:flex;align-items:stretch;
    position:fixed;left:0;right:0;bottom:0;z-index:120;
    height:calc(var(--m-tabbar-h) + env(safe-area-inset-bottom));
    padding:0 4px env(safe-area-inset-bottom);
    background:var(--surface);border-top:1px solid var(--border);
  }
  .m-tab{
    flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;
    background:none;border:none;padding:0;cursor:pointer;position:relative;
    font-family:'DM Sans',sans-serif;font-size:10px;font-weight:500;color:var(--text-muted);
  }
  .m-tab.active{color:var(--brand);font-weight:600;}
  .m-tab.active svg{stroke-width:2.4;}
  .m-tab-plus{flex:0 0 62px;display:flex;align-items:center;justify-content:center;background:none;border:none;padding:0;cursor:pointer;}
  .m-tab-plus-circle{
    width:44px;height:44px;border-radius:50%;background:var(--brand);color:#fff;
    display:flex;align-items:center;justify-content:center;
    box-shadow:0 4px 12px rgba(26,58,42,.35);transition:transform .12s;
  }
  .m-tab-plus:active .m-tab-plus-circle{transform:scale(.92);}
  /* Content must not end hidden underneath the fixed bar */
  #studentApp .page:not(#page-messages){padding-bottom:calc(var(--m-tabbar-h) + env(safe-area-inset-bottom) + 8px);}
  /* Filter bottom sheet slides over the tab bar — keep its footer clear of the home indicator */
  .filter-drawer{padding-bottom:env(safe-area-inset-bottom);}
  .msg-layout{height:calc(100dvh - var(--m-topbar-h) - env(safe-area-inset-top) - var(--m-tabbar-h) - env(safe-area-inset-bottom));}
  .toast{bottom:calc(var(--m-tabbar-h) + env(safe-area-inset-bottom) + 16px);}
  /* MESSAGES: one pane at a time (WhatsApp-style). Default = full-width list;
     body.chat-open = full-screen conversation with its own header/composer chrome. */
  .msg-sidebar,.msg-sidebar.msg-collapsed{width:100%;border-right:none;}
  .msg-toggle-btn{display:none;}                                   /* desktop collapse arrow */
  .msg-sidebar.msg-collapsed~.chat-area .msg-reopen-btn{display:none;} /* desktop reopen arrow */
  .chat-area{display:none;}
  body.chat-open .msg-sidebar{display:none;}
  body.chat-open .chat-area{display:flex;position:fixed;inset:0;height:100dvh;z-index:200;}
  body.chat-open .s-nav,body.chat-open .m-tabbar{display:none;}
  body.chat-open .chat-header{padding:calc(12px + env(safe-area-inset-top)) 14px 12px;}
  body.chat-open .chat-messages{padding:16px 12px 12px;}
  body.chat-open .chat-input-area{padding:10px 12px calc(10px + env(safe-area-inset-bottom));}
  .m-back{display:flex;align-items:center;}
  .reply-hover{display:none;} /* touch replies via bubble swipe, not hover */
}

/* ============================================================
   SHARED PATTERNS lifted out of JS-generated HTML (L6, 2026-07-13)
   These replace inline style="..." strings that were being repeated
   at many call sites — and, worse, emitted by helper functions that
   were defined three and four times over. Declarations here are
   byte-for-byte what the inline styles said, so nothing shifts.
   ============================================================ */

/* Small uppercase label above a group of admin filter controls.
   (was: secHead(), defined identically in 3 places) */
.a-field-label{font-size:11px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:8px;}

/* A wrapping row of filter chips. (was: chipRow(), defined in 3 places) */
.a-chip-row{display:flex;gap:6px;flex-wrap:wrap;}

/* Status pill sized to sit inside a flex row without stretching it.
   (was: an inline font-size/flex-shrink on 2 of the 4 sPill() copies) */
.pill-compact{font-size:11px;flex-shrink:0;}

/* Uppercase label above a group of controls in the student filter drawer.
   (was: the same inline string repeated 11 times in listings.js) */
.df-label{font-size:11px;font-weight:700;color:var(--text-muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px;}


/* ---- Patterns lifted out of index.html (L6 phase 2, 2026-07-13) ----
   Every declaration below is exactly what the inline style it replaced said.
   Proven by a computed-style diff of all 1,543 elements before vs after.

   NOT moved here: the 28 inline `display:none` attributes. The JS resets
   elements with `el.style.display = ''` to fall back to their default — if
   display:none lived in a class, that reset would re-hide them instead of
   showing them. Those inline styles are load-bearing. */

.label-hint{font-weight:400;color:var(--text-faint);}
.hiw-card-title{font-family:'DM Serif Display',serif;font-size:21px;line-height:1.2;margin-bottom:var(--sp-2);}
.hiw-card-text{font-size:14px;color:var(--text-muted);line-height:1.6;}
/* A how-it-works card that is itself a button (the "Sign up with your .edu" one).
   The whole card is the tap target — the link line below is the visible cue. */
.hiw-card-link{cursor:pointer;transition:border-color .15s,box-shadow .15s,transform .15s;}
.hiw-card-link:hover,.hiw-card-link:focus-visible{border-color:var(--brand-light);box-shadow:var(--shadow-md);transform:translateY(-2px);}
.hiw-card-cta{display:inline-flex;align-items:center;gap:5px;margin-top:12px;font-size:13.5px;font-weight:600;color:var(--accent-warm);}
.hiw-card-link:hover .hiw-card-cta{gap:9px;}
.exp-body{padding:16px;}
.exp-desc{font-size:13px;color:var(--text-muted);margin-bottom:12px;line-height:1.6;}
.exp-count{font-size:12px;color:var(--text-faint);margin-bottom:12px;}
.exp-actions{display:flex;gap:8px;}
.exp-btn{flex:1;padding:9px;}
.bg-alt{background:var(--bg-alt);}

/* ------------------------------------------------------------------ LANDING, BELOW THE FOLD
   Sections alternate cream and sand; the cards are the white thing. Pure-white sections
   were the one surface on the page that appears nowhere in the app itself.

   THE DARK BAND. .btn-primary became brand green in 4.1 so it would read on cream — which
   made it brand green on a brand-green band: 1.00:1, a button whose shape had vanished and
   whose label floated on nothing. On a dark band the primary action flips to the accent,
   6.0:1 against the band and 6.0:1 for its own label. The focus ring flips to white for the
   same reason: the usual --brand-light ring is 2.4:1 against this ground. */
.home-section.band-dark{background:var(--brand);color:#fff;}
.band-center{text-align:center;}
.band-badge{display:inline-block;background:rgba(255,255,255,.12);border:1px solid rgba(255,255,255,.2);
  color:rgba(255,255,255,.85);font-size:var(--fs-small);font-weight:500;padding:5px 14px;
  border-radius:var(--radius-pill);margin-bottom:var(--sp-5);}
.band-h2{font-family:'DM Serif Display',serif;font-size:clamp(28px,4vw,38px);color:#fff;
  line-height:1.15;margin-bottom:var(--sp-3);}
.band-lead{color:rgba(255,255,255,.72);font-size:var(--fs-lead);max-width:520px;
  margin:0 auto var(--sp-6);line-height:1.7;}
.band-dark .btn-primary{background:var(--accent);color:var(--brand);box-shadow:0 6px 20px rgba(0,0,0,.25);}
.band-dark .btn-primary:hover{background:#e09600;}
.band-dark .btn-primary:focus-visible{outline-color:#fff;}

.home-inner-narrow{max-width:660px;}
.home-narrow{max-width:620px;}
.home-h2-left{text-align:left;}
.home-center{text-align:center;}
.home-prose{color:var(--text-muted);font-size:var(--fs-lead);line-height:1.8;margin-bottom:var(--sp-5);}
.home-prose:last-child{margin-bottom:0;}
.home-strong{color:var(--text);}
.home-lead-gap{margin-bottom:var(--sp-6);}
.home-cta{margin-top:var(--sp-6);text-decoration:none;}
.home-cta-email{text-decoration:none;}
/* Muted, not faint: "we reply within 1-2 business days" is a promise, and --text-faint
   measures 2.9:1 on these grounds. */
.home-note{color:var(--text-muted);font-size:var(--fs-small);margin-top:var(--sp-3);}

@media(max-width:640px){
  .home-section{padding:var(--sp-6) var(--sp-4);}
  .hiw-grid,.feat-grid,.who-grid{gap:var(--sp-3);}
  .hiw-card{padding:22px 20px 24px;}
  /* The email address IS the label, and at 15.5px it is wider than some phone columns.
     Let it wrap inside a taller button rather than overflow the page sideways. */
  .home-cta-email{height:auto;min-height:54px;padding:12px 22px;white-space:normal;
    overflow-wrap:anywhere;text-align:center;justify-content:center;max-width:100%;}
}

.check-label{font-size:13px;display:flex;align-items:center;gap:4px;cursor:pointer;}
.modal-narrow{max-width:420px;}
.table-scroll{overflow-x:auto;}
.form-label-sm{font-size:11px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.04em;display:block;margin-bottom:4px;}
.link-brand{color:var(--brand);cursor:pointer;}
.btn-nowrap{white-space:nowrap;}

/* Category colours. These read the --cat-* variables in :root, which are the single
   source of truth — js/config.js reads the same variables. Nothing is hardcoded twice. */
.cat-housing{background:var(--cat-housing-bg);color:var(--cat-housing-text);}
.cat-clothing{background:var(--cat-clothing-bg);color:var(--cat-clothing-text);}
.cat-technology{background:var(--cat-technology-bg);color:var(--cat-technology-text);}
.cat-donation{background:var(--cat-donation-bg);color:var(--cat-donation-text);}
.cat-books{background:var(--cat-books-bg);color:var(--cat-books-text);}
.cat-other{background:var(--cat-other-bg);color:var(--cat-other-text);}

/* Book-type colours — mirror BOOK_COLORS in js/books.js. */
.btype-course{background:var(--btype-course-bg);color:var(--btype-course-text);}
.btype-other{background:var(--btype-other-bg);color:var(--btype-other-text);}

.tcard-flush{margin-bottom:0;}
.note-xs{font-size:11px;color:var(--text-faint);}
/* Sits under the "what are you posting?" grid. Events left this form on 2026-09-07 and
   this line is where the option used to be — a removed choice with no explanation reads
   as a bug to the person who used it last week. */
.post-cat-note{margin-top:14px;font-size:12px;color:var(--text-muted);line-height:1.6;text-align:center;}

/* The school / brand chip. It had been hand-written 18 times in 12 slightly different
   variants across admin.js — 9/10/11/12px fonts and six different paddings — so the same
   badge looked subtly different on every admin screen. It is ONE size now: 11px reads
   cleanly inside a dense table row, and is not shouty when it stands alone in a filter
   panel. (The old 12px crowded table cells; the old 9px was too small to read.) */
.school-chip{background:var(--brand-pale);color:var(--brand);border-radius:20px;font-weight:500;text-transform:capitalize;font-size:11px;padding:2px 8px;}

/* The "Loading…" block shown while a detail drawer fetches. */
.drawer-loading{padding:60px 0;text-align:center;color:var(--text-faint);font-size:13px;}

/* ── Admin: organizations tab ──────────────────────────────────────────────── */
/* The indented tree that lived here (.org-tree, .org-depth-*, .org-row, .org-type, .org-actions)
   was replaced on 2026-09-14 by the grouped list styled under .ao-* further down. What stays is
   shared: .org-name is used by events.js, and .org-badge, .org-btn and the roster rules are used
   by the org console and the officer panel. */
.org-name { flex: 1; font-weight: 600; font-size: 14px; color: var(--text); }

.org-badge {
  display: inline-block; margin-left: 6px; padding: 2px 7px;
  border-radius: 20px; font-size: 10px; font-weight: 600; vertical-align: middle;
}
.org-badge-ok  { background: var(--brand-pale, #eef3ff); color: var(--brand, #3B5BA5); }
.org-badge-off { background: var(--bg-alt); color: var(--text-muted); }

.org-btn {
  padding: 6px 11px; font-size: 12px; font-weight: 500;
  border: 1px solid var(--border); border-radius: 6px;
  background: var(--bg); color: var(--text); cursor: pointer;
}
.org-btn:hover { background: var(--brand-pale, #eef3ff); border-color: var(--brand, #3B5BA5); }
.org-btn-warn:hover { background: #fdecec; border-color: #c0392b; color: #c0392b; }
/* The one action in a row that moves the thing forward — publish an event, start a new one.
   Filled rather than outlined, so a row of five identical buttons has an obvious first
   choice instead of making the officer read all five every time. */
.org-btn-go { background: var(--brand, #3B5BA5); border-color: var(--brand, #3B5BA5); color: #fff; }
.org-btn-go:hover { background: var(--brand); border-color: var(--brand); color: #fff; }

.org-panel:not(:empty) {
  margin: 4px 0 8px 24px; padding: 12px 14px;
  border: 1px solid var(--border); border-radius: 8px; background: var(--bg);
}
.org-roster-row {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 0; border-bottom: 1px solid var(--border);
}
.org-roster-row:last-of-type { border-bottom: none; }
.org-roster-who  { flex: 1; font-size: 13px; color: var(--text); }
.org-roster-role { font-size: 11px; color: var(--text-faint); }

.org-form { display: flex; gap: 8px; margin-top: 10px; }
.org-input {
  flex: 1; padding: 7px 10px; font-size: 13px;
  border: 1px solid var(--border); border-radius: 6px;
  background: var(--surface); color: var(--text);
}
.org-empty { padding: 16px; font-size: 13px; color: var(--text-muted); line-height: 1.6; }

/* A removed member is history, not a member. Dimmed rather than hidden, so an officer can
   see who used to serve and restore them — and so removal never looks like deletion. The
   name stays legible on purpose: this is a record, and a record you cannot read is not one. */
.org-roster-row-off .org-roster-who  { color: var(--text-muted); text-decoration: line-through; }
.org-roster-row-off .org-roster-role { font-style: italic; }

/* Sits under the add-officer form. Explains a limit rather than labelling a control, so it
   is quieter than .org-empty and indented to read as a footnote to the field above it. */
.org-note {
  margin-top: 6px; padding: 0 2px;
  font-size: 12px; line-height: 1.5; color: var(--text-faint);
}

/* ── Admin: Organizations tab (2026-09-14, canvas page 5) ──────────────────────
   One list grouped by department. A number the page cannot know is drawn as "—"
   (.ao-unknown), never as 0 — the reasoning is in _aoFacts() in js/orgs.js.
   Every colour is a :root token, and each text/background pair clears AA. */
.ao-page { display: flex; flex-direction: column; gap: 18px; }

.ao-toolbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.ao-tabs { display: flex; gap: 8px; flex-wrap: wrap; }
.ao-tab {
  display: inline-flex; align-items: center; gap: 7px;
  height: 34px; padding: 0 15px; border-radius: var(--radius-pill);
  border: 1px solid var(--border); background: var(--surface); color: var(--text);
  font: 500 13px/1 'DM Sans', sans-serif; cursor: pointer;
}
.ao-tab:hover { border-color: var(--brand-light); }
.ao-tab.is-on { background: var(--brand); border-color: var(--brand); color: #fff; font-weight: 600; }
.ao-tab-n { color: var(--text-muted); font-weight: 500; }
.ao-tab.is-on .ao-tab-n { color: rgba(255, 255, 255, .8); }
.ao-tab-n.is-warn,
.ao-tab.is-on .ao-tab-n.is-warn {
  min-width: 20px; height: 20px; line-height: 20px; padding: 0 6px; border-radius: 10px; text-align: center;
  background: var(--cat-donation-bg); color: var(--cat-donation-text); font-size: var(--fs-micro); font-weight: 700;
}
.ao-search {
  margin-left: auto; display: flex; align-items: center; gap: 8px;
  width: 260px; max-width: 100%; height: 36px; padding: 0 13px;
  border: 1px solid var(--border); border-radius: var(--radius-pill);
  background: var(--surface); color: var(--text-faint);
}
/* The ring sits on the pill, not the bare input inside it, so focus is drawn where the eye expects it. */
.ao-search:focus-within { border-color: var(--brand-light); outline: var(--focus-ring); outline-offset: var(--focus-offset); }
.ao-search input {
  flex: 1; min-width: 0; border: 0; outline: 0; background: transparent;
  font: 400 13px 'DM Sans', sans-serif; color: var(--text);
}
.ao-search input::placeholder { color: var(--text-faint); }

.ao-grid { display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); gap: 16px; align-items: start; }
.ao-side { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
.ao-card { min-width: 0; background: var(--surface); border: 1px solid var(--border); border-radius: 16px; }
.ao-side .ao-card { display: flex; flex-direction: column; gap: 13px; padding: 18px 20px 20px; }
.ao-list-card { overflow: hidden; }
.ao-card-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.ao-list-card .ao-card-head { flex-wrap: wrap; padding: 15px 20px; border-bottom: 1px solid var(--border); }
.ao-card-title { margin: 0; font-size: 15px; font-weight: 600; color: var(--text); }
.ao-card-note { font-size: 13px; color: var(--text-faint); }
.ao-card-lede { margin: 4px 0 0; font-size: 13px; line-height: 1.5; color: var(--text-muted); }
.ao-empty { margin: 0; font-size: 13px; line-height: 1.55; color: var(--text-muted); }
.ao-school {
  padding: 14px 20px 6px; font-size: var(--fs-micro); font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--text-muted);
}

.ao-cols, .ao-row {
  display: grid; align-items: center; column-gap: 10px;
  grid-template-columns: minmax(0, 1fr) 76px 56px 62px 124px 34px;
  padding: 10px 20px;
}
.ao-cols {
  padding-top: 9px; padding-bottom: 9px; border-bottom: 1px solid var(--border);
  font-size: var(--fs-micro); font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--text-faint);
}
.ao-cols span:nth-child(2), .ao-cols span:nth-child(3), .ao-cols span:nth-child(4) { text-align: right; }
.ao-cols span:nth-child(5) { padding-left: 12px; }
.ao-row { border-bottom: 1px solid var(--border); }
.ao-row.is-open { background: var(--bg); }
.ao-row.is-open, .ao-dept.is-open { border-bottom-color: transparent; }
.ao-row.is-off .ao-name { color: var(--text-muted); }
.ao-who { display: flex; align-items: center; gap: 11px; min-width: 0; }
.ao-tile { flex: none; width: 34px; height: 34px; border-radius: 11px; object-fit: cover; }
.ao-tile.dir-logo-none {
  display: flex; align-items: center; justify-content: center;
  background: var(--tint-bg, var(--bg-alt)); color: var(--tint-fg, var(--text-muted));
  font-size: 13px; font-weight: 700;
}
.ao-who-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ao-name { font-size: 13px; font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ao-sub { font-size: var(--fs-micro); color: var(--text-faint); }
.ao-sub.is-warn { color: var(--cat-donation-text); font-weight: 600; }
/* On a wide screen the three numbers are grid cells of the row itself; on a phone they become one line. */
.ao-nums { display: contents; }
.ao-num { text-align: right; font-size: 13px; font-weight: 600; color: var(--text); font-variant-numeric: tabular-nums; }
.ao-num.is-warn { color: var(--cat-donation-text); font-weight: 700; }
.ao-unknown { color: var(--text-faint); font-weight: 500; cursor: help; }
/* Heard by screen readers everywhere; seen only on a phone, where there are no column headings. */
.ao-num-l {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}
.ao-status { padding-left: 12px; }
.ao-badge {
  display: inline-flex; align-items: center; gap: 5px; padding: 3px 9px; border-radius: var(--radius-pill);
  font-size: var(--fs-micro); font-weight: 700; white-space: nowrap;
}
.ao-badge::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.ao-badge.is-ok   { background: var(--brand-pale); color: var(--cat-housing-text); }
.ao-badge.is-warn { background: var(--cat-donation-bg); color: var(--cat-donation-text); }
.ao-badge.is-off  { background: var(--bg-alt); color: var(--text-muted); }
.ao-more, .ao-more-none { justify-self: end; }
.ao-more {
  width: 32px; height: 32px; border-radius: 50%; border: 1px solid var(--border);
  background: var(--surface); color: var(--text-muted);
  display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
}
.ao-more:hover { border-color: var(--brand-light); color: var(--text); }
.ao-more.is-open { background: var(--brand); border-color: var(--brand); color: #fff; }

.ao-dept {
  display: flex; align-items: center; gap: 11px; flex-wrap: wrap;
  padding: 9px 20px; background: var(--bg); border-bottom: 1px solid var(--border);
}
.ao-dept-icon {
  flex: none; width: 28px; height: 28px; border-radius: 9px;
  display: flex; align-items: center; justify-content: center;
  background: var(--brand-pale); color: var(--cat-housing-text);
}
.ao-dept-text { flex: 1; min-width: 0; display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.ao-dept-name { font-size: 13px; font-weight: 700; color: var(--text); }
.ao-dept.is-off .ao-dept-name { color: var(--text-muted); }
.ao-dept-meta { font-size: var(--fs-micro); color: var(--text-faint); }
.ao-dept-actions { display: flex; align-items: center; gap: 6px; }

.ao-btn {
  height: 30px; padding: 0 12px; border-radius: var(--radius-pill);
  border: 1px solid var(--border); background: var(--surface); color: var(--text);
  font: 600 13px/1 'DM Sans', sans-serif; white-space: nowrap; cursor: pointer;
}
.ao-btn:hover { border-color: var(--brand-light); }
.ao-btn:disabled { opacity: .55; cursor: default; }
.ao-btn-go { background: var(--brand); border-color: var(--brand); color: #fff; }
.ao-btn-go:hover { background: var(--brand-mid); border-color: var(--brand-mid); }
.ao-btn-warn { color: var(--danger); }
.ao-btn-warn:hover { border-color: var(--danger); background: var(--danger-pale); }
.ao-btn-danger { height: 36px; background: var(--danger); border-color: var(--danger); color: #fff; }
.ao-btn-danger:hover { filter: brightness(.94); border-color: var(--danger); }
.ao-link {
  display: inline-flex; align-items: center; gap: 2px; padding: 0; border: 0; background: none;
  font: 600 13px 'DM Sans', sans-serif; color: var(--brand-mid); cursor: pointer;
}
.ao-link:hover { text-decoration: underline; }

.ao-strip {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 2px 20px 14px 65px; background: var(--bg); border-bottom: 1px solid var(--border);
}
.ao-suspend { flex-direction: column; align-items: stretch; gap: 11px; }
.ao-suspend-copy { display: flex; flex-direction: column; gap: 3px; font-size: 13px; line-height: 1.5; }
.ao-suspend-copy strong { color: var(--text); }
.ao-suspend-copy span { color: var(--text-muted); }
.ao-reasons { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.ao-reasons-label {
  margin-right: 3px; font-size: var(--fs-micro); font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase; color: var(--text-faint);
}
.ao-reason {
  height: 30px; padding: 0 12px; border-radius: var(--radius-pill);
  border: 1px solid var(--border); background: var(--surface); color: var(--text);
  font: 500 13px/1 'DM Sans', sans-serif; cursor: pointer;
}
.ao-reason.is-on { background: var(--brand); border-color: var(--brand); color: #fff; font-weight: 600; }
.ao-suspend-row { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.ao-suspend-row .ao-input { flex: 1 1 220px; width: auto; }

.ao-field { display: flex; flex-direction: column; gap: 6px; }
.ao-field label { font-size: 13px; font-weight: 600; color: var(--text); }
.ao-optional { font-weight: 400; color: var(--text-faint); }
.ao-input {
  box-sizing: border-box; width: 100%; height: 38px; padding: 0 12px;
  border: 1px solid var(--border); border-radius: 10px; background: var(--surface);
  font: 400 13px 'DM Sans', sans-serif; color: var(--text);
}
.ao-input::placeholder { color: var(--text-faint); }
select.ao-input { cursor: pointer; }
.ao-hint { font-size: var(--fs-micro); color: var(--text-faint); }
.ao-add-actions { display: flex; align-items: center; justify-content: flex-end; gap: 12px; flex-wrap: wrap; }
.ao-add-actions .ao-link { margin-right: auto; }
.ao-add-actions .ao-btn-go { height: 38px; padding: 0 18px; }

.ao-dec-list { display: flex; flex-direction: column; gap: 13px; margin: 0; padding: 0; list-style: none; }
.ao-dec { display: flex; gap: 10px; }
.ao-dec-dot { flex: none; width: 7px; height: 7px; margin-top: 6px; border-radius: 50%; background: var(--brand-light); }
.ao-dec-dot.is-off { background: var(--danger); }
.ao-dec-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; font-size: 13px; line-height: 1.45; color: var(--text); }
.ao-dec-meta { font-size: var(--fs-micro); color: var(--text-faint); overflow-wrap: anywhere; }

/* The officer panel predates this page and is shared; here it opens flush under its row. */
.ao-page .org-panel:not(:empty) {
  margin: 0; padding: 12px 20px 14px 65px; border: 0; border-bottom: 1px solid var(--border);
  border-radius: 0; background: var(--bg);
}

.ao-tab:focus-visible, .ao-btn:focus-visible, .ao-more:focus-visible, .ao-reason:focus-visible,
.ao-link:focus-visible, .ao-input:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); }

@media (max-width: 1200px) {
  .ao-grid { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 768px) {
  .ao-search { margin-left: 0; width: 100%; }
  .ao-cols { display: none; }
  .ao-list-card .ao-card-head { padding: 13px 14px; }
  .ao-row { grid-template-columns: minmax(0, 1fr) auto auto; row-gap: 6px; padding: 12px 14px; }
  .ao-who { grid-column: 1; grid-row: 1; }
  .ao-status { grid-column: 2; grid-row: 1; padding-left: 0; }
  .ao-more, .ao-more-none { grid-column: 3; grid-row: 1; }
  .ao-more { width: 40px; height: 40px; }
  .ao-nums { display: flex; flex-wrap: wrap; gap: 4px 14px; grid-column: 1 / -1; grid-row: 2; padding-left: 45px; }
  .ao-num { text-align: left; }
  .ao-num-l { position: static; width: auto; height: auto; margin: 0; overflow: visible; clip: auto; font-weight: 400; color: var(--text-muted); }
  .ao-dept, .ao-strip, .ao-school, .ao-page .org-panel:not(:empty) { padding-left: 14px; padding-right: 14px; }
  .ao-suspend-row .ao-input { flex-basis: 100%; }
}

/* ── Notification bell + history (2026-09-04) ──────────────────────────────── */
/* Deliberately NOT .nav-btn. The mobile rule `#navUser .nav-btn{display:none}`
   hides those below 768px, and a campus app is read mostly on phones — the bell
   has to survive there, so it sits beside .nav-avatar, which also survives. */
/* Line-art to match ico() and the mobile tab bar, not an emoji. listings.js:657
   says it outright: "use these app-wide, not emojis". `currentColor` is what makes
   it inherit the nav's white, the way every other icon in the app does. */
.nav-bell {
  position: relative; display: inline-flex; align-items: center; justify-content: center;
  background: none; border: none; cursor: pointer; padding: 4px 6px;
  color: rgba(255,255,255,0.8);
  transition: color .15s;
}
.nav-bell:hover { color: #fff; }

/* Desktop destination links. .nav-btn is what the ≤768px rule hides, so these disappear
   on a phone and the bottom bar carries the same five places instead. */
.nav-link{background:none;color:rgba(255,255,255,0.8);}
.nav-link:hover{color:#fff;}
/* Post is an action, not a destination — so it is the one filled thing in the bar, and
   it is the desktop counterpart of the + in the middle of the phone bar. */
.nav-post{display:inline-flex;align-items:center;gap:6px;background:var(--accent);color:var(--brand);
  font-weight:600;padding:7px 14px;border-radius:var(--radius-sm);border:none;cursor:pointer;
  font-family:'DM Sans',sans-serif;font-size:var(--fs-small);}
.nav-post:hover{background:#ffb61f;}
/* The unread count keeps the amber it had as a tab badge; .notif-badge alone is the red
   used for notifications, and the two should not look like the same kind of alert. */
.notif-badge-msg{background:var(--accent);color:var(--brand);}

.notif-badge {
  display: none;                 /* .show is added only when the count is > 0 */
  position: absolute; top: -2px; right: -2px;
  min-width: 16px; padding: 0 4px;
  background: #c0392b; color: #fff;
  font-size: 10px; font-weight: 700; line-height: 16px;
  border-radius: 9px; text-align: center;
}
.notif-badge.show { display: block; }

.notif-row { padding: 12px 0; border-bottom: 1px solid var(--border); }
.notif-row:last-child { border-bottom: none; }
.notif-msg  { font-size: 13px; line-height: 1.6; color: var(--text); }
.notif-time { font-size: 11px; color: var(--text-faint); margin-top: 4px; }

/* Unread gets a brand-coloured rail rather than a background wash, so a long
   list of unread items stays readable instead of turning into a block of colour. */
.notif-unread {
  border-left: 3px solid var(--brand, #3B5BA5);
  padding-left: 10px; margin-left: -13px;
}
.notif-empty { padding: 20px 0; font-size: 13px; color: var(--text-muted); line-height: 1.6; }

/* ── Org console (workstream 2) ────────────────────────────────────────────── */
/* The identity bar is sticky on purpose. Every action taken in here is taken ON
   BEHALF of an organization, and the plan (§3) is explicit that it must never be
   ambiguous which one — so it stays on screen while the body scrolls. */
.oc-top { display: flex; align-items: center; justify-content: space-between; gap: 12px;
          padding: 12px 18px 4px; }
.oc-back { border: 0; background: none; padding: 6px 0; cursor: pointer; font-family: inherit;
           font-size: 13.5px; color: var(--text-muted); }
.oc-back:hover { color: var(--text); }
.oc-switch { height: 30px; padding: 0 12px; border-radius: var(--radius-pill); cursor: pointer;
             border: 1px solid var(--border); background: var(--surface); font-family: inherit;
             font-size: 12px; font-weight: 600; color: var(--text); }
.oc-switch:hover { border-color: var(--brand-light); }
/* The band on the cream page, not a green slab: the green is the student app's own bar on
   desktop, and two green bars stacked read as one broken one. It keeps its stickiness. */
.oc-bar {
  position: sticky; top: 0; z-index: 10;
  padding: 10px 18px 12px;
  background: var(--bg); border-bottom: 1px solid var(--border);
}
.oc-identity { display: flex; align-items: center; gap: 13px; min-width: 0;
                font-size: 15px; font-weight: 600; color: var(--text); }
.oc-id-logo { width: 50px; height: 50px; border-radius: 15px; object-fit: cover; flex: none; }
.oc-id-logo.dir-logo-none { font-size: 19px; }
.oc-id-text { min-width: 0; display: flex; flex-direction: column; gap: 5px; }
.oc-id-name { font-family: 'DM Serif Display', Georgia, serif; font-size: 22px; font-weight: 400;
              line-height: 1.12; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.oc-id-meta { display: flex; align-items: center; gap: 8px; }
/* The warm-sand category pair, 5.47:1. The mockup's own ochre was 4.38:1 — under AA for text
   this small. */
.oc-role-pill { background: var(--cat-donation-bg); color: var(--cat-donation-text);
                font-size: 10px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
                padding: 3px 8px; border-radius: var(--radius-pill); }
.oc-id-type { font-size: 12px; font-weight: 400; color: var(--text-muted); text-transform: capitalize; }
.oc-stats { display: flex; padding: 14px 18px 4px; }

/* One row that scrolls sideways on a phone rather than wrapping into two — five tabs at 390px
   wrapped into a ragged second line that looked like a second menu. */
.oc-nav { display: flex; gap: 24px; padding: 0 18px; border-bottom: 1px solid var(--border);
          overflow-x: auto; scrollbar-width: none; }
.oc-nav::-webkit-scrollbar { display: none; }
.oc-tab {
  flex: none; white-space: nowrap; padding: 12px 0 11px; margin-bottom: -1px;
  font-size: 14px; font-weight: 500; font-family: inherit;
  background: none; border: none; border-bottom: 2.5px solid transparent;
  color: var(--text-muted); cursor: pointer;
}
.oc-tab:hover { color: var(--text); }
.oc-tab.active { color: var(--brand); font-weight: 600; border-bottom-color: var(--brand); }
/* Listed so the shape of the console is visible, disabled so nothing pretends to work. */
.oc-tab-soon { opacity: .4; cursor: not-allowed; }

/* Centred, not pinned left. At 620px hard-left on a 1900px screen the console read as a
   column that had failed to load the rest of itself. */
.oc-body { padding: 18px; max-width: var(--col-work); margin-inline: auto; }
.oc-meta { display: flex; align-items: center; gap: 10px; margin-bottom: 18px; }
.oc-meta-type { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--text-muted); }
.oc-meta-slug { font-size: 12px; color: var(--text-muted); font-family: ui-monospace, monospace; }

.oc-field { display: block; margin-bottom: 14px; }
.oc-label { display: block; font-size: 12px; font-weight: 600; color: var(--text-muted); margin-bottom: 5px; }
.oc-input {
  width: 100%; padding: 9px 11px; font: inherit; font-size: 14px;
  border: 1px solid var(--border); border-radius: 7px;
  background: var(--surface); color: var(--text);
}
.oc-input:disabled { background: var(--bg); color: var(--text-muted); cursor: not-allowed; }
.oc-save { margin-top: 6px; }
.oc-note { font-size: 12px; color: var(--text-muted); line-height: 1.6; margin-top: 12px; }
/* Date, start and end on one line. They are read together — "Tuesday, 6 to 8" is one
   thought — and stacking them makes the officer scroll to answer a single question.
   Wraps on a narrow phone rather than shrinking three controls until none is tappable. */
.oc-ev-row { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 8px; }

/* ---------- Events section ---------- */
/* One full-width action above the list, as the mockup has it. */
.oc-cta { display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%;
             height: 46px; margin-bottom: 14px; border: 0; border-radius: var(--radius-pill);
             background: var(--brand); color: #fff; cursor: pointer; font-family: inherit;
             font-size: 14px; font-weight: 600; }
.oc-cta:hover { background: var(--brand-mid); }
.oc-cta:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); }
/* The honest line under "+ New post" about what posting does and does not do. */
.oc-post-note { margin: -4px 2px 16px; font-size: 12.5px; line-height: 1.55; color: var(--text-muted); }
.oc-ev-filters { margin-bottom: 12px; }
/* Each bucket carries its count, so an officer can see there are drafts waiting without
   opening them. */
.oc-ev-count { display: inline-block; min-width: 18px; height: 18px; line-height: 18px; padding: 0 5px;
               margin-left: 6px; border-radius: 9px; background: var(--bg-alt); color: var(--text-muted);
               font-size: 10.5px; font-weight: 700; text-align: center; }
.oc-ev-filter.is-on .oc-ev-count { background: rgba(255,255,255,.22); color: #fff; }
.oc-ev-empty { margin-top: 18px; padding: 26px 18px; text-align: center; font-size: 14px;
               color: var(--text-muted); border: 1px dashed var(--border);
               border-radius: 14px; line-height: 1.7; }

/* The card is a two-column grid on anything wider than a phone: poster left at a fixed
   width, everything else right. Fixed rather than proportional so a row of cards has one
   text column that starts in the same place — a ragged left edge down a list is the thing
   that makes it look unconsidered. */
.oc-ev-card { display: grid; grid-template-columns: 132px 1fr; gap: 16px; padding: 14px;
              margin-bottom: 12px; border: 1px solid var(--border);
              border-radius: 16px; background: var(--surface); box-shadow: var(--shadow-sm); }
.oc-ev-card.is-cancelled { border-color: var(--danger, #c0392b); opacity: .78; }
@media (max-width: 560px) {
  /* One column on a phone. The poster keeps its 4:5 shape but stops being a stripe down the
     side, where at 132px it would crowd the text into a gutter. */
  .oc-ev-card { grid-template-columns: 1fr; gap: 12px; }
  .oc-ev-poster { max-width: 200px; }
}

/* Posters are portrait (~4:5), which is why they get their own bucket and their own box.
   object-fit: cover crops rather than distorting, so a landscape photo dropped in by an
   officer fills the frame instead of stretching a face sideways. */
.oc-ev-poster { aspect-ratio: 4/5; border-radius: 10px; overflow: hidden;
                background: var(--bg-alt); }
.oc-ev-poster img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* The generated poster. Three placed things and nothing else: the org at the top, the title
   filling the middle, the date anchored to the bottom. Space-between does the placing, so a
   one-word title and a five-word title both sit correctly without any measuring. */
.oc-ev-made { display: flex; flex-direction: column; justify-content: space-between;
              padding: 12px 12px 13px; color: #fff; }
.oc-ev-p-org { font-size: 9px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
               opacity: .85; line-height: 1.3;
               /* Two lines at most. A long department name would otherwise push the title
                  down and unbalance every card it appears on. */
               display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
               overflow: hidden; }
.oc-ev-p-title { font-family: 'DM Serif Display', Georgia, serif; font-size: 19px;
                 line-height: 1.15; margin: 8px 0;
                 display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical;
                 overflow: hidden; }
.oc-ev-p-foot { font-size: 10px; letter-spacing: .02em; opacity: .95; }
/* A short rule above the date. It gives the bottom block a baseline to sit on, so a poster
   with a two-word title does not leave the date floating in the middle of nowhere. */
.oc-ev-p-rule { display: block; width: 26px; height: 2px; background: currentColor;
                opacity: .7; margin-bottom: 6px; }

/* The text column: one idea per line, in the order an officer reads them — what it is called,
   when, where, then the numbers. Size and colour carry the ranking, not indentation. */
.oc-ev-body { min-width: 0; display: flex; flex-direction: column; }
.oc-ev-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }
.oc-ev-title { font-size: 16px; font-weight: 600; color: var(--text); line-height: 1.3; }
.oc-ev-when  { margin-top: 4px; font-size: 13px; color: var(--text); }
.oc-ev-where { margin-top: 2px; font-size: 13px; color: var(--text-muted); }
.oc-ev-facts { margin-top: 8px; font-size: 12px; color: var(--text-muted); }
.oc-ev-reason { margin-top: 8px; font-size: 12px; color: var(--danger, #c0392b); line-height: 1.6; }
/* Pushed to the bottom of the card so the action row sits on one line across every card in
   the list, whatever length the text above it happens to be. */
.oc-ev-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: auto; padding-top: 12px; }
/* Seven tools in a row read as a toolbar when they are small pills rather than seven boxes.
   Every one stays visible: "Who's coming" and QR are what an officer reaches for at the door,
   and a Manage menu in front of them would slow down the moment that matters most.
   Two classes, so these outrank the shared .org-btn wherever it sits — which means
   .org-btn-go has to be restated here or Publish would lose its green. */
.oc-ev-actions .org-btn, .oc-post-actions .org-btn, .oc-member .org-btn { height: 30px; padding: 0 12px; border-radius: var(--radius-pill);
                          background: var(--surface); font-size: 12px; }
.oc-ev-actions .org-btn-go, .oc-post-actions .org-btn-go, .oc-member .org-btn-go { background: var(--brand); border-color: var(--brand); color: #fff; }

/* The registration list, expanded under one event at a time. */
.oc-ev-roster { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border); }
.oc-reg-head { font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
               color: var(--text-faint); margin-bottom: 8px; }
/* Name and email on separate lines rather than one run-on row: the email is what gets copied
   and read back character by character, and it needs to be scannable on its own. */
.oc-reg-row { display: flex; align-items: center; justify-content: space-between; gap: 12px;
              padding: 8px 0; border-bottom: 1px solid var(--border); }
.oc-reg-row:last-of-type { border-bottom: 0; }
.oc-reg-row.is-off { opacity: .5; }
.oc-reg-who { min-width: 0; }
.oc-reg-name { font-size: 13px; font-weight: 600; color: var(--text); }
.oc-reg-mail { font-size: 12px; color: var(--text-muted); word-break: break-all; }
.oc-reg-state { flex: none; font-size: 11px; color: var(--text-faint); text-align: right; }

/* The count an officer glances at between people. Big number first, context after — a door is
   read at arm's length while somebody is talking to you. */
.oc-door-count { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
                 margin-bottom: 10px; }
.oc-door-in { font-family: 'DM Serif Display', Georgia, serif; font-size: 30px;
              line-height: 1; color: var(--brand, #3B5BA5); }
.oc-door-of { font-size: 13px; color: var(--text); }
.oc-door-note { font-size: 11px; color: var(--text-faint); }

.oc-door-search { margin: 0 0 6px; }
.oc-door-actions { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 12px; }

/* The walk-in form, below the actions rather than above the list. It is used less often than
   the list is read, and putting it on top would push the queue down every time it opened. */
.oc-walk { margin-top: 12px; padding: 14px; border: 1px solid var(--border);
           border-radius: 10px; background: var(--bg); }
.oc-walk .ff:last-of-type { margin-bottom: 8px; }

/* Somebody already in the room recedes. The list is a queue of people still to deal with, and
   the ones you have dealt with should stop competing for the same attention. */
.oc-reg-row.is-here { opacity: .68; }
.oc-reg-in { color: var(--success, #2e7d52); font-weight: 600; text-align: right; }
/* Undo is the only warm-coloured control in the list, because it is the only one that is
   time-limited — it has five seconds to be noticed. */
.oc-undo { border-color: #c0392b; color: #c0392b; }
.oc-undo:hover { background: #fdecec; border-color: #c0392b; }

.oc-ev-drop { display: block; border: 2px dashed var(--border); border-radius: var(--radius-sm);
              padding: 16px; text-align: center; cursor: pointer; color: var(--text-muted);
              font-size: 13px; margin-top: 10px; }
.oc-ev-drop:hover { border-color: var(--brand); }

/* Scrolls sideways rather than wrapping: a row that reflows as photos are added makes the
   thumbnail under your finger move somewhere else between taps. */
.oc-ev-strip { display: flex; gap: 8px; overflow-x: auto; padding: 10px 0 4px; }
.oc-ev-thumb { position: relative; flex: 0 0 82px; height: 102px; border-radius: 8px;
               overflow: hidden; background: var(--bg-alt); }
.oc-ev-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.oc-ev-cover { position: absolute; left: 0; bottom: 0; width: 100%; padding: 3px 0;
               font-size: 10px; text-align: center; background: rgba(0,0,0,.62); color: #fff;
               border: 0; }
.oc-ev-cover-btn { cursor: pointer; font-family: inherit; }
.oc-ev-x { position: absolute; top: 3px; right: 3px; width: 20px; height: 20px; border: 0;
           border-radius: 50%; background: rgba(0,0,0,.62); color: #fff; cursor: pointer;
           line-height: 1; font-size: 14px; }
.oc-subhead {
  font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: .06em;
  color: var(--text-muted); margin: 18px 0 8px;
}

.oc-member-list { background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
                  overflow: hidden; margin-bottom: 6px; }
/* Someone waiting to be let in, on a warm ground so the request is the first thing seen. */
.oc-member-list.is-pending { background: var(--accent-pale); }
.oc-member { display: flex; align-items: center; gap: 10px; padding: 11px 14px; border-bottom: 1px solid var(--border); }
.oc-member:last-of-type { border-bottom: none; }
.oc-member-av { flex: none; width: 34px; height: 34px; border-radius: 50%; }
.oc-member-av.dir-logo-none { font-size: 12px; font-weight: 700; }
.oc-member-who  { flex: 1; min-width: 0; font-size: 14px; font-weight: 600; color: var(--text);
                  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.oc-member-role { flex: none; font-size: 10.5px; font-weight: 600; color: var(--text-muted);
                  background: var(--bg-alt); padding: 2px 8px; border-radius: var(--radius-pill);
                  text-transform: capitalize; }

.oc-pick { padding: 18px; max-width: 480px; }
.oc-pick-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  width: 100%; margin-bottom: 8px; padding: 14px;
  border: 1px solid var(--border); border-radius: 9px;
  background: var(--surface); cursor: pointer; text-align: left;
}
.oc-pick-row:hover { border-color: var(--brand, #3B5BA5); background: var(--brand-pale, #eef3ff); }
.oc-pick-name { font-size: 14px; font-weight: 600; color: var(--text); }
.oc-pick-role { font-size: 11px; color: var(--text-faint); }

.oc-entry { margin-top: 16px; background: var(--brand-pale, #eef3ff); color: var(--brand, #3B5BA5); border: 1px solid var(--brand, #3B5BA5); }

/* ── Org console: posts, polls, logo ───────────────────────────────────────── */
.oc-composer { border: 1px solid var(--border); border-radius: 16px; box-shadow: var(--shadow-sm); padding: 14px; margin-bottom: 20px; background: var(--surface); }
.oc-composer .oc-input { margin-bottom: 8px; }
.oc-type-row { display: flex; gap: 6px; margin-bottom: 10px; }
.oc-type {
  padding: 6px 13px; font-size: 12px; font-weight: 600; cursor: pointer;
  border: 1px solid var(--border); border-radius: 20px;
  background: var(--bg); color: var(--text-muted);
}
.oc-type.active { background: var(--brand, #3B5BA5); border-color: var(--brand, #3B5BA5); color: #fff; }
.oc-poll-fields { padding: 4px 0; }
.oc-toggle-row { display: flex; gap: 14px; flex-wrap: wrap; margin: 10px 0 4px; }
.oc-toggle { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-muted); cursor: pointer; }

/* ---------- Event form ---------- */
/* Every field is a labelled row in one vertical rhythm. The screenshot this replaced had
   three unlabelled boxes in a line — mm/dd/yyyy, --:--, --:-- — where nothing said which
   time was the start. A placeholder is not a label: it disappears exactly when the field
   has content, which is when you most want to check what it was for. */
.oc-ev-form { padding: 18px; }
.ff-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
           margin-bottom: 14px; }
.ff-head-title { font-family: 'DM Serif Display', Georgia, serif; font-size: 20px;
                 color: var(--text); margin: 0; }
.ff-head-x { border: 0; background: none; font-size: 12px; color: var(--text-muted);
             cursor: pointer; font-family: inherit; text-decoration: underline; padding: 0; }
.ff-warn { margin: 0 0 14px; padding: 10px 12px; border-radius: 8px;
           background: var(--warning-pale, #fdf6e3); color: var(--warning, #8a6d1f);
           font-size: 12px; line-height: 1.6; }

.ff { margin-bottom: 12px; }
.ff-label { display: block; margin-bottom: 5px; font-size: 12px; font-weight: 600;
            color: var(--text); }
.ff-opt { font-weight: 400; color: var(--text-faint); }
.ff .oc-input { margin: 0; }
/* Help sits under the thing it explains and is visibly subordinate to it. Same size as a
   label but lighter and not bold, so it never competes for the same read. */
.ff-help { margin: -4px 0 14px; font-size: 12px; color: var(--text-muted); line-height: 1.6; }
.ff-help-center { text-align: center; margin-top: 10px; }

/* Date wider than the two times, because a date is longer. Below 560px the date takes the
   full row and the two times split it — start and end belong side by side, always. */
.ff-when { display: grid; grid-template-columns: 1.5fr 1fr 1fr; gap: 10px; margin-bottom: 12px; }
.ff-when .ff { margin-bottom: 0; }
@media (max-width: 560px) {
  .ff-when { grid-template-columns: 1fr 1fr; }
  .ff-when-date { grid-column: 1 / -1; }
}

/* A disclosure is a full-width row, not a pill. Pills floating in the middle of a column of
   full-width fields read as leftover buttons; a row reads as a section of the form that is
   currently shut. Each states what is inside before it is opened, so nobody has to open one
   to find out whether they already filled it in. */
.ff-section { display: flex; align-items: center; gap: 10px; width: 100%; padding: 12px 14px;
              margin-top: 6px; border: 1px solid var(--border); border-radius: 8px;
              background: var(--bg); cursor: pointer; font-family: inherit; text-align: left; }
.ff-section:hover { border-color: var(--brand, #3B5BA5); }
.ff-section.is-open { border-bottom-left-radius: 0; border-bottom-right-radius: 0;
                      border-bottom-color: transparent; }
.ff-section-label { font-size: 13px; font-weight: 600; color: var(--text); }
.ff-section-state { margin-left: auto; font-size: 12px; color: var(--text-faint); }
/* A plus that becomes a minus. Two spans would need managing in JS; one box with a rotating
   pseudo-element cannot fall out of sync with the panel it controls. */
.ff-section-mark { position: relative; width: 12px; height: 12px; flex: none; }
.ff-section-mark::before, .ff-section-mark::after {
  content: ''; position: absolute; background: var(--text-muted);
  transition: transform .18s ease, opacity .18s ease; }
.ff-section-mark::before { left: 0; top: 5px; width: 12px; height: 2px; }
.ff-section-mark::after  { left: 5px; top: 0; width: 2px; height: 12px; }
.ff-section.is-open .ff-section-mark::after { transform: scaleY(0); opacity: 0; }

.ff-panel { padding: 14px; margin-bottom: 6px; border: 1px solid var(--border);
            border-top: 0; border-radius: 0 0 8px 8px; background: var(--bg); }
.ff-panel .ff:last-child { margin-bottom: 0; }
.ff-panel .ff-help:last-child { margin-bottom: 0; }

/* Two actions, equal width, side by side. Draft first because it reads left-to-right as the
   lesser commitment, and publish sits where the eye finishes. A full-width Publish with a
   small draft button underneath said the draft was an afterthought — it is not, it is half
   of what this form does. */
.ff-actions { display: flex; gap: 10px; margin-top: 18px; }
.ff-btn { flex: 1 1 0; padding: 13px 10px; border-radius: 8px; font-size: 14px;
          font-weight: 600; cursor: pointer; font-family: inherit;
          border: 1px solid var(--border); background: var(--bg); color: var(--text); }
.ff-btn-ghost:hover { border-color: var(--brand, #3B5BA5); color: var(--brand, #3B5BA5); }
.ff-btn-go { background: var(--brand, #3B5BA5); border-color: var(--brand, #3B5BA5); color: #fff; }
.ff-btn-go:hover { background: var(--brand); border-color: var(--brand); }
.ff-btn:disabled { opacity: .6; cursor: default; }

.oc-post { border: 1px solid var(--border); border-radius: 16px; box-shadow: var(--shadow-sm); padding: 14px; margin-bottom: 12px; background: var(--surface); }
/* Urgent is a left rail, not a filled card. A red block behind body text hurts to read, and
   an urgent post is the one people most need to actually read. */
.oc-post-urgent { border-left: 3px solid #c0392b; }
.oc-post-head { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-bottom: 7px; }
.oc-post-date { margin-left: auto; font-size: 11px; color: var(--text-muted); }
.oc-post-title { font-size: 15px; font-weight: 600; color: var(--text); margin-bottom: 5px; }
.oc-post-body { font-size: 13px; line-height: 1.6; color: var(--text); white-space: pre-wrap; }
.oc-post-actions { display: flex; gap: 6px; margin-top: 11px; }
.oc-chip {
  padding: 2px 8px; border-radius: 20px; font-size: 10px; font-weight: 600;
  background: var(--bg); color: var(--text-muted); border: 1px solid var(--border);
}
.oc-chip-pin    { background: var(--brand-pale, #eef3ff); color: var(--brand, #3B5BA5); border-color: transparent; }
.oc-chip-urgent { background: #fdecec; color: #c0392b; border-color: transparent; }

.oc-poll { margin-top: 11px; }
.oc-opt-vote {
  display: block; width: 100%; text-align: left; margin-bottom: 6px; padding: 10px 12px;
  font-size: 13px; border: 1px solid var(--border); border-radius: 8px;
  background: var(--bg); color: var(--text); cursor: pointer;
}
.oc-opt-vote:hover { border-color: var(--brand, #3B5BA5); background: var(--brand-pale, #eef3ff); }
/* The bar sits behind the label rather than beside it, so the option text stays at a
   consistent position however the vote splits. */
.oc-opt-result {
  position: relative; display: flex; align-items: center; gap: 8px;
  margin-bottom: 6px; padding: 10px 12px; overflow: hidden;
  border: 1px solid var(--border); border-radius: 8px; font-size: 13px;
}
.oc-opt-result.mine { border-color: var(--brand, #3B5BA5); }
.oc-opt-bar { position: absolute; inset: 0 auto 0 0; background: var(--brand-pale, #eef3ff); }
.oc-opt-label { position: relative; flex: 1; color: var(--text); }
.oc-opt-count { position: relative; font-weight: 600; color: var(--text-muted); }

.oc-logo-row { display: flex; align-items: center; gap: 14px; margin-bottom: 18px; }
/* The club's tile from _dirLogoHTML(): an <img> when there is a logo, otherwise its initial on
   the club's tint — the same tile as the directory, the club page and the console's band. */
.oc-logo {
  width: 64px; height: 64px; border-radius: 16px; overflow: hidden; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center; object-fit: cover;
  background: var(--bg-alt); border: 1px solid var(--border);
}
.oc-logo.dir-logo-none { font-size: 24px; }
.oc-logo-btn { cursor: pointer; }

/* ── Organization directory (2026-09-06) ──────────────────────────────────────
   The first student-facing surface on the org hierarchy. Rendered by
   js/orgdir.js into #page-orgs.

   No dark-mode block: this stylesheet has none anywhere, and adding one for a
   single new page would leave the rest of the app inconsistent in a way a
   reader would have to notice and explain. Everything below is built from the
   same --surface / --border / --text tokens as the rest of the app, so it
   inherits whatever theme work lands later. */

/* The subtitle under a page title. Defined as a class rather than repeated as
   an inline style — the listings page still carries the inline version, which
   is the migration CLAUDE.md calls slow future cleanup. New markup uses this. */
.page-sub { color: var(--text-muted); font-size: 13px; margin-top: 4px; }

.dir-controls { display: flex; gap: 10px; align-items: center; margin-bottom: 14px; }
.dir-search-wrap { position: relative; flex: 1; }
.dir-search-icon {
  position: absolute; left: 13px; top: 50%; transform: translateY(-50%);
  color: var(--text-faint); pointer-events: none; display: flex;
}
/* The mockup's white pill. The <form> shell around the input is an autofill guard — Chrome
   pools inputs outside a form and pours saved credentials into the first visible one — and it
   is untouched here. */
.dir-search { margin-bottom: 0; width: 100%; padding-left: 40px; height: 44px;
              border-radius: var(--radius-pill); background: var(--surface); }

.dir-list { display: flex; flex-direction: column; gap: 10px; }

/* FOLLOWING — the clubs you follow, as tiles above the list, on the unfiltered directory only. */
.dir-following { margin: 2px 0 18px; }
.dir-fol-lab { font-size: 11px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
               color: var(--text-muted); margin-bottom: 10px; }
.dir-fol-row { display: flex; gap: 10px; overflow-x: auto; scrollbar-width: none; padding-bottom: 2px; }
.dir-fol-row::-webkit-scrollbar { display: none; }
.dir-fol { flex: none; width: 104px; display: flex; flex-direction: column; align-items: center;
           gap: 8px; padding: 13px 8px; background: var(--surface); border: 1px solid var(--border);
           border-radius: 16px; cursor: pointer; font-family: inherit; }
.dir-fol:hover { border-color: var(--brand-light); }
.dir-fol:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); }
.dir-fol-logo { width: 44px; height: 44px; border-radius: 14px; object-fit: cover; }
.dir-fol .dir-logo-none { font-size: 14px; }
.dir-fol-name { font-size: 11.5px; font-weight: 600; color: var(--text); text-align: center;
                line-height: 1.3; display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2;
                -webkit-box-orient: vertical; overflow: hidden; }

/* Six tints for the initial tiles, borrowed from the category tokens so they stay in the
   palette. A separate attribute from data-cat on purpose: a club is not "housing", and a
   future reader should not have to wonder why it says so. */
[data-tint="1"]{--tint-bg:var(--cat-housing-bg);   --tint-fg:var(--cat-housing-text);}
[data-tint="2"]{--tint-bg:var(--cat-clothing-bg);  --tint-fg:var(--cat-clothing-text);}
[data-tint="3"]{--tint-bg:var(--cat-technology-bg);--tint-fg:var(--cat-technology-text);}
[data-tint="4"]{--tint-bg:var(--cat-donation-bg);  --tint-fg:var(--cat-donation-text);}
[data-tint="5"]{--tint-bg:var(--cat-books-bg);     --tint-fg:var(--cat-books-text);}
[data-tint="6"]{--tint-bg:var(--cat-other-bg);     --tint-fg:var(--cat-other-text);}

/* One object repeated, so the edges, baselines and inner padding match from
   card to card and the follow button sits in the same place on every row. */
.dir-card {
  display: grid; grid-template-columns: auto 1fr auto;
  gap: 14px; align-items: center;
  padding: 14px 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 16px;
}

.dir-logo {
  width: 48px; height: 48px; border-radius: 15px;
  object-fit: cover; flex-shrink: 0; background: var(--bg);
}
.dir-logo-none {
  display: flex; align-items: center; justify-content: center;
  background: var(--tint-bg, var(--brand-pale)); color: var(--tint-fg, var(--brand-mid));
  font-size: 20px; font-weight: 700;
}

.dir-body { min-width: 0; }   /* lets the ellipsis below actually engage */

/* The breadcrumb is where the hierarchy first becomes visible to a student,
   so it sits above the name rather than below it — read before the club, the
   way it is spoken: Caldwell University > Student Life > Chess Club. */
.dir-crumb {
  font-size: 11px; color: var(--text-muted); margin-bottom: 3px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.dir-sep { opacity: .6; padding: 0 1px; }

.dir-name {
  font-size: 15px; font-weight: 600; color: var(--text);
  margin: 0 0 3px; line-height: 1.3;
}
.dir-verified {
  color: var(--success); font-size: 12px; margin-left: 5px; font-weight: 700;
}

/* Two lines, then ellipsis. A one-line description and a five-line one must
   not make two cards different heights, or the column stops reading as a list. */
.dir-desc {
  font-size: 13px; color: var(--text-muted); line-height: 1.5; margin: 0 0 5px;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}

.dir-meta { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-muted); }
.dir-type { text-transform: capitalize; }
.dir-dot { opacity: .5; }
.dir-count { font-variant-numeric: tabular-nums; }

/* Follow is the lightest commitment in the product, so the resting state is a
   quiet outline rather than a solid call to action. Once following, it inverts
   to filled-pale — clearly a state, and still obviously clickable to undo. */
.dir-follow {
  flex-shrink: 0; padding: 7px 16px; font-size: 13px; font-weight: 600;
  border-radius: 999px; cursor: pointer;
  border: 1px solid var(--brand-light);
  background: var(--surface); color: var(--brand-mid);
  transition: background .12s, border-color .12s, color .12s;
}
.dir-follow:hover { background: var(--brand-pale); }
.dir-follow.is-following {
  background: var(--brand-pale); border-color: var(--brand-pale); color: var(--brand-mid);
}
.dir-follow.is-following:hover {
  background: var(--danger-pale); border-color: var(--danger); color: var(--danger);
}
.dir-follow:focus-visible { outline: 2px solid var(--brand-mid); outline-offset: 2px; }

/* "Following" states a fact and hides an action — nothing on it says that tapping unfollows.
   The label becomes "Unfollow" exactly while the pointer or keyboard focus is on it, which is
   also when the button turns red. A phone has no hover, which is why the toast exists: on
   touch the confirmation arrives after the tap instead of before it. */
.df-do { display: none; }
.dir-follow.is-following:hover .df-is,
.dir-follow.is-following:focus-visible .df-is { display: none; }
.dir-follow.is-following:hover .df-do,
.dir-follow.is-following:focus-visible .df-do { display: inline; }
/* Held wide enough for the longer of the two words, so the button does not jump under the
   pointer at the moment it changes meaning. */
.dir-follow { min-width: 96px; text-align: center; }

.dir-empty {
  padding: 40px 20px; text-align: center;
  font-size: 14px; color: var(--text-muted); line-height: 1.7;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius);
}

/* Narrow: the logo shrinks and the follow button drops to its own row rather
   than squeezing the name into two characters and an ellipsis. */
@media(max-width:600px) {
  .dir-card { grid-template-columns: auto 1fr; gap: 12px; padding: 12px 14px; }
  .dir-logo { width: 40px; height: 40px; font-size: 17px; }
  .dir-follow { grid-column: 1 / -1; width: 100%; padding: 9px 16px; }
}

/* A tappable row on the profile page: icon, title, one line of explanation, chevron.
   Built for the directory entry point, which needs a home on mobile because the bottom
   bar holds five tabs and all five are spoken for.

   Deliberately not a .btn-full — that reads as an action ("Log out"), and this is
   navigation. Left-aligned with a chevron is the convention every phone settings screen
   uses for "this opens somewhere else", which is exactly what it does. */
.profile-row {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 11px 14px; margin-top: 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  cursor: pointer; text-align: left;
  transition: background .12s, border-color .12s;
}
.profile-row:hover { background: var(--brand-pale); border-color: var(--brand-light); }
.profile-row:focus-visible { outline: 2px solid var(--brand-mid); outline-offset: 2px; }

.profile-row-icon {
  display: flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; flex-shrink: 0;
  border-radius: var(--radius-sm);
  background: var(--brand-pale); color: var(--brand-mid);
}
.profile-row-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.profile-row-title { font-size: 14px; font-weight: 600; color: var(--text); }
.profile-row-sub   { font-size: 12px; color: var(--text-muted); line-height: 1.45; }
.profile-row-chev  { color: var(--text-faint); font-size: 20px; line-height: 1; flex-shrink: 0; }

/* "You" where a Remove button would be, on your own roster row. A label rather than a
   disabled button: guard_org_self_removal() refuses the write, so a control here could only
   ever produce an error message. Sized to sit on the same baseline as the buttons it
   replaces so the column does not jump. */
.org-roster-self {
  font-family: inherit; font-size: 11px; font-weight: 600;
  color: var(--text-muted); letter-spacing: .04em; text-transform: uppercase;
  padding: 6px 10px; flex-shrink: 0;
}


/* ============================================================
   STUDENT EVENTS FEED
   Poster-first, chronological, date-grouped. Deliberately NOT the listing card: an event is
   read as "when is this and who is running it", where a listing is read as "what is it and
   what does it cost". Sharing a card would have forced both into whichever shape was written
   first.
   ============================================================ */

/* A poster-first feed is a COLUMN, not a page-width table. At full width a 4:5 poster is
   either enormous or, worse, sits in the corner of a card with a field of white beside it —
   which is what the first version did. Capping the column is what makes the poster the right
   size at every window width without a single media query about the poster itself. */
#evFeed { max-width: var(--col-read); margin-inline: auto; }

/* Sticky so the day you are looking at stays named while you scroll through it. A feed of
   cards with no visible day is a feed you have to remember your way through. */
.ev-day { position: sticky; top: 0; z-index: 2; padding: 14px 0 8px;
          font-size: 12px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
          color: var(--text-muted); background: var(--bg); }

.ev-card { border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden;
           background: var(--surface); margin-bottom: var(--sp-4); box-shadow: var(--shadow-sm); }

/* The org row is first, above the poster, because "who is running this" is the question the
   verified badge answers and it should be answered before the poster is admired. */
.ev-org { display: flex; align-items: center; gap: 8px; width: 100%; padding: 10px 12px;
          border: 0; border-bottom: 1px solid var(--border); background: none;
          cursor: pointer; font-family: inherit; text-align: left; }
.ev-org-logo { width: 22px; height: 22px; border-radius: 50%; object-fit: cover; flex: none; }
.ev-org-logo-blank { background: var(--bg-alt); display: inline-block; }
.ev-org-name { font-size: 13px; font-weight: 600; color: var(--text); }
.ev-verified { font-size: 11px; color: #fff; background: var(--brand, #3B5BA5);
               width: 15px; height: 15px; border-radius: 50%; display: inline-flex;
               align-items: center; justify-content: center; flex: none; }

/* 4:5 portrait, the aspect the event-media bucket exists to keep consistent. */
/* No max-height. The column above caps the width, and the ratio does the rest — a max-height
   as well would fight the ratio and letterbox the poster at some widths and not others. */
.ev-poster { position: relative; aspect-ratio: 4/5; background: var(--bg-alt); }
/* The tappable area is a child, so the save button sitting over it is not inside the thing
   that opens the event — no stopPropagation needed to keep one from firing the other. */
.ev-poster-hit { position: absolute; inset: 0; cursor: pointer; }
.ev-poster-img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ev-poster-made { width: 100%; height: 100%; display: flex; flex-direction: column;
                  justify-content: space-between; padding: 18px; color: #fff; }
.ev-p-org { font-size: 11px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
            opacity: .85; }
/* Placed by space-between, not by measuring: org at the top, title taking the middle, the
   when/where block anchored to the bottom. A one-word title and a six-word title both sit
   correctly, and nothing has to know how long the text is. */
.ev-p-title { font-family: 'DM Serif Display', Georgia, serif; font-size: 30px; line-height: 1.15;
              margin: 14px 0; display: -webkit-box; -webkit-line-clamp: 4;
              -webkit-box-orient: vertical; overflow: hidden; }
.ev-p-foot { font-size: 13px; line-height: 1.5; }
/* A short rule gives the bottom block a baseline to sit on. Without it a short title leaves
   the date floating in the middle of the poster with nothing under it. */
.ev-p-rule { display: block; width: 34px; height: 2px; background: currentColor;
             opacity: .75; margin-bottom: 8px; }
.ev-p-when { font-weight: 600; }
.ev-p-where { opacity: .85; }

/* Sized down when the poster already carries the words. An empty text column beside a star
   would leave the card looking like it failed to render its own title. */
.ev-meta { display: flex; align-items: center; gap: 10px; padding: 12px 14px 14px; }
.ev-meta-text { flex: 1; min-width: 0; cursor: pointer; }
.ev-title { font-size: 16px; font-weight: 600; color: var(--text); line-height: 1.3; }
/* Muted, not faint: "You're going · 42 going" is the most useful line on the card and
   --text-faint measures 2.9:1 on white. Sits in the footer now, so no margin of its own. */
.ev-seats { font-size: 12px; color: var(--text-muted); }
.ev-seats strong { color: var(--text); font-weight: 600; }
.ev-going { display: inline-flex; align-items: center; gap: 3px; color: var(--brand); font-weight: 600; }
/* Time and place, one per row, each with its icon — the mockup's card body. The text truncates
   rather than wrapping so a long venue name cannot make one card taller than its neighbours. */
.ev-row { display: flex; align-items: center; gap: 6px; margin-top: 4px; min-width: 0;
          font-size: 12.5px; color: var(--text-muted); }
.ev-row svg { flex: none; }
.ev-row span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ev-foot { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
.ev-meta-text > .ev-foot:first-child { margin-top: 0; }
/* The filter row. White chips on the cream page, like the mockup; .evs-chip supplies the shape
   and the selected state the search already uses, so both filter UIs look like one thing. */
.ev-chips, .dir-chips, .oc-ev-filters { display: flex; gap: var(--sp-2); overflow-x: auto; scrollbar-width: none;
            padding: 2px 0 var(--sp-2); }
.ev-chips::-webkit-scrollbar, .dir-chips::-webkit-scrollbar, .oc-ev-filters::-webkit-scrollbar { display: none; }
/* Two classes, not one: the shared .evs-chip base further down would otherwise win on source
   order and paint these cream with its own padding — which is what the 5.2 Events chips
   actually shipped with until 5.3a. The .is-on rules below carry the same weight and come
   later, so a selected chip still turns green. */
.ev-chips .ev-fchip, .dir-chips .dir-chip, .oc-ev-filters .oc-ev-filter { flex: none; white-space: nowrap; background: var(--surface); height: 34px;
            padding: 0 15px; font-size: 12.5px; font-weight: 600; }
.ev-note-btn { background: none; border: 0; padding: 0; margin-left: 4px; cursor: pointer;
               font: inherit; font-weight: 600; color: var(--accent-warm); }

/* The star is private and the calendar icon (registration, E3) is not. They must never look
   alike, so the star is an outline that fills — a state you set — and registration will be a
   button with words on it. */
/* Overlaid on the poster, same treatment as the search grid tile: its own translucent ground
   so it stays legible over a photograph and over a generated gradient alike. Declared after
   .fav-btn in this file would be tidier, but !important-free overrides need only come later
   than the rule they narrow — and .fav-btn is further down, so these carry a class weight of
   their own via .ev-card. */
.ev-card .ev-fav { position: absolute; top: 10px; right: 10px; width: 36px; height: 36px;
                   background: rgba(255,255,255,.86); backdrop-filter: blur(3px); }
.ev-card .ev-fav svg { width: 19px; height: 19px; }
.ev-card .ev-fav:hover { background: #fff; }

.ev-past-chip { display: block; width: 100%; margin: 22px 0 12px; padding: 11px;
                border: 1px solid var(--border); border-radius: 10px; background: var(--bg);
                font-size: 13px; color: var(--text-muted); cursor: pointer; font-family: inherit; }
.ev-past-chip:hover { border-color: var(--brand, #3B5BA5); }
/* Past events are greyed rather than hidden. They are still worth opening — recap photos are
   the reason the chip exists — but they must never compete with something you can still go to. */
.ev-past .ev-card { opacity: .72; }

.ev-note { padding: 20px 4px; font-size: 13px; color: var(--text-muted); }
/* Shape shared with .evs-none and .sq-empty — see the shared block at the end of this file.
   Only the button below is particular to this one. */
.ev-empty-btn { padding: 10px 18px; border-radius: 8px; border: 1px solid var(--brand, #3B5BA5);
                background: var(--brand, #3B5BA5); color: #fff; font-size: 14px;
                font-weight: 600; cursor: pointer; font-family: inherit; }

@media (max-width: 768px) {
  #evFeed { max-width: none; }      /* the phone IS the column */
}


/* ---------- Event detail ---------- */
.evd-modal { max-width: 560px; }
#evDetailBody { padding: 0 24px 26px; }

/* The banner is first and it is loud, because for a registrant it is the ONLY way they find
   out — there is no notification layer, so this is the whole of §6's mitigation. */
.evd-cancelled { padding: 12px 14px; margin-bottom: 16px; border-radius: 10px;
                 background: #fdecec; color: #c0392b; font-size: 13px; line-height: 1.6; }

.evd-poster { aspect-ratio: 4/5; max-height: 380px; border-radius: 12px; overflow: hidden;
              background: var(--bg-alt); }
.evd-poster img { width: 100%; height: 100%; object-fit: cover; display: block; }
.evd-org { border: 0; border-bottom: 1px solid var(--border); padding: 12px 0; }
.evd-title { font-family: 'DM Serif Display', Georgia, serif; font-size: 26px; line-height: 1.2;
             color: var(--text); margin: 14px 0 10px; }
.evd-when  { font-size: 15px; color: var(--text); font-weight: 500; }
.evd-where { font-size: 14px; color: var(--text-muted); margin-top: 2px; }

.evd-cal { display: flex; gap: 8px; flex-wrap: wrap; margin: 14px 0 4px; }
.evd-cal-btn { padding: 8px 12px; font-size: 12px; border: 1px solid var(--border);
               border-radius: 8px; background: var(--bg); color: var(--text); cursor: pointer;
               font-family: inherit; }
.evd-cal-btn:hover { border-color: var(--brand, #3B5BA5); color: var(--brand, #3B5BA5); }

.evd-desc { font-size: 14px; line-height: 1.75; color: var(--text); margin: 16px 0; }
.evd-gallery { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; margin: 16px 0; }
.evd-gallery img { width: 100%; aspect-ratio: 1; object-fit: cover; border-radius: 8px; }

.evd-video { display: flex; align-items: center; gap: 10px; padding: 12px 14px; margin: 8px 0;
             border: 1px solid var(--border); border-radius: 10px; text-decoration: none;
             color: var(--text); font-size: 13px; }
.evd-video:hover { border-color: var(--brand, #3B5BA5); }
.evd-video-play { width: 26px; height: 26px; border-radius: 50%; flex: none; color: #fff;
                  background: var(--brand, #3B5BA5); display: inline-flex;
                  align-items: center; justify-content: center; font-size: 11px; }

.evd-reg { display: flex; align-items: center; gap: 12px; margin-top: 22px; }
.evd-btn { flex: 1; padding: 14px; border-radius: 10px; font-size: 15px; font-weight: 600;
           cursor: pointer; font-family: inherit; border: 1px solid var(--border);
           background: var(--bg); color: var(--text); }
.evd-btn-go { background: var(--brand, #3B5BA5); border-color: var(--brand, #3B5BA5); color: #fff; }
.evd-btn-go:hover { background: var(--brand); }
.evd-btn-ghost { flex: none; padding: 10px 14px; font-size: 13px; font-weight: 500; }
.evd-btn:disabled { opacity: .55; cursor: default; }
.evd-going { flex: 1; font-size: 15px; font-weight: 600; color: var(--brand, #3B5BA5); }
.evd-seats { font-size: 13px; color: var(--text-muted); flex: none; }

/* Directly under the button, not in a settings page, not in a tooltip. §4.1 says do not bury
   it, and the reason is that the star and the register button differ on exactly this point. */
.evd-privacy { margin: 10px 0 0; font-size: 12px; color: var(--text-muted); line-height: 1.6; }
.evd-note { margin-top: 20px; padding: 12px 14px; border-radius: 10px;
            background: var(--bg-alt); font-size: 13px; color: var(--text-muted);
            line-height: 1.6; }

/* Both of these are STATES, not messages. A toast would fade while the student is still
   standing at the door wondering whether their tap landed — and tapping again is the natural
   response to a screen that shows nothing. */
.evd-waiting { margin-top: 22px; padding: 16px; border-radius: 12px;
               background: var(--warning-pale, #fdf6e3); color: var(--warning, #8a6d1f);
               font-size: 14px; line-height: 1.6; }
.evd-waiting strong { display: block; margin-bottom: 4px; }
.evd-here { margin-top: 22px; padding: 18px; border-radius: 12px; text-align: center;
            background: var(--success-pale, #eaf6ef); color: var(--success, #2e7d52);
            font-size: 16px; font-weight: 600; }


/* ============================================================
   ONE ORGANIZATION
   ============================================================ */
.op-back { border: 0; background: none; padding: 0; margin-bottom: 16px; cursor: pointer;
           font-family: inherit; font-size: 13px; color: var(--text-muted); }
.op-back:hover { color: var(--brand, #3B5BA5); }

/* The club's own colour. orgPagePaint() sets data-tint on #orgPageBody — the same tint the club's
   tile carries in the directory — so the band, the logo tile and every date block below inherit
   one colour. Decorative: org_directory has no cover image to show here. */
.op-cover { height: 150px; border-radius: var(--radius-lg);
            background: linear-gradient(150deg, rgba(255,255,255,.45) 0%, rgba(255,255,255,0) 65%),
                        var(--tint-bg, var(--brand-pale)); }
/* The logo sits on the band's lower edge, ringed in the page colour so it reads as lifted off it. */
.op-head { display: flex; flex-direction: column; align-items: flex-start; gap: 12px;
           margin-top: -42px; position: relative; }
.op-logo { width: 74px; height: 74px; border-radius: 22px; object-fit: cover; flex: none;
            background: var(--surface); box-shadow: 0 0 0 4px var(--bg); }
/* The initial tile comes from _dirLogoHTML(), so it shares the directory's tint; this only
   scales its letter up to the bigger tile. Two classes, to outrank .dir-logo-none. */
.op-logo.dir-logo-none { font-size: 28px; font-weight: 600; }
.op-head-text { flex: 1; min-width: 0; }
.op-name { font-family: 'DM Serif Display', Georgia, serif; font-size: 26px; line-height: 1.2;
           color: var(--text); margin: 2px 0 4px; }
.op-meta { font-size: 13px; color: var(--text-muted); text-transform: capitalize; }

.op-desc { font-size: 14px; line-height: 1.65; color: var(--text); margin: 16px 0 0; }

.op-sec { margin-top: 30px; }
/* Section headings are a rung above the rows beneath them, in the same small-caps register the
   console uses, so the page reads as sections rather than as one long column. */
.op-sec-title { font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
                color: var(--text-muted); margin: 0 0 12px; }

.op-officers { display: flex; flex-wrap: wrap; gap: 10px; }
.op-officer { padding: 10px 14px; border: 1px solid var(--border); border-radius: 10px; }
.op-officer-name { font-size: 13px; font-weight: 600; color: var(--text); }
.op-officer-role { font-size: 12px; color: var(--text-muted); }

/* A compact row, not the feed's full-height poster. This page is a summary of an
   organization; a column of posters would bury the description under the events. */
/* A date block where the poster thumbnail was: on a page that is a summary of one club, WHEN is
   what the rows are for, and the date reads at a glance where a poster at 56px does not. The
   tint is the club's, inherited from #orgPageBody. */
.op-date { flex: none; width: 48px; height: 52px; border-radius: 12px; display: flex;
           flex-direction: column; align-items: center; justify-content: center;
           background: var(--tint-bg, var(--brand-pale)); color: var(--tint-fg, var(--brand-mid)); }
.op-date-dow { font-size: 9.5px; font-weight: 700; letter-spacing: .08em; }
.op-date-day { font-family: 'DM Serif Display', Georgia, serif; font-size: 20px; line-height: 1.05; }
.op-going { color: var(--brand); font-weight: 600; }
.op-past .op-event { opacity: .68; }

.op-post { padding: 14px; margin-bottom: 8px; border: 1px solid var(--border);
           border-radius: 14px; background: var(--surface); }
.op-post.is-urgent { border-color: #c0392b; }
.op-post-head { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-bottom: 6px; }
.op-post-date { margin-left: auto; font-size: 11px; color: var(--text-muted); }
.op-post-title { font-size: 15px; font-weight: 600; color: var(--text); }
.op-post-body { font-size: 14px; line-height: 1.7; color: var(--text-muted); margin-top: 4px;
                white-space: pre-wrap; }

.op-note { padding: 16px 0; font-size: 13px; color: var(--text-muted); }

/* STATS — the profile's light-ruled columns, not a box. Each counts something on this page. */
.op-stats { display: flex; margin-top: 16px; }
.op-stat { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 1px;
           border-right: 1px solid var(--border); }
.op-stat:last-child { border-right: 0; }
.op-stat-n { font-family: 'DM Serif Display', Georgia, serif; font-size: 19px; line-height: 1.15;
             color: var(--text); font-variant-numeric: tabular-nums; }
.op-stat-l { font-size: 11px; color: var(--text-muted); }
/* Follow at full width — on this page it is THE action, not one button in a row of cards. */
.op-follow-row { display: flex; margin-top: 16px; }
.op-follow-row .dir-follow { flex: 1; height: 46px; font-size: 14.5px; }
/* Contact, as rows in one card rather than links strung together with dots. Only the rows that
   exist are drawn — an empty "Website —" says nothing but that a form was skipped. */
.op-info { margin-top: 16px; background: var(--surface); border: 1px solid var(--border);
           border-radius: 14px; overflow: hidden; }
.op-info-row { display: flex; align-items: center; gap: 12px; padding: 12px 15px;
               border-bottom: 1px solid var(--border); }
.op-info-row:last-child { border-bottom: 0; }
.op-info-k { flex: none; font-size: 13.5px; color: var(--text); }
.op-info-v { flex: 1; min-width: 0; text-align: right; font-size: 12.5px; color: var(--text-muted);
             overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.op-info-v a { color: var(--brand); text-decoration: none; }
.op-info-v a:hover { text-decoration: underline; }

@media (max-width: 560px) {
  .op-head { flex-wrap: wrap; }
  .op-head .dir-follow { order: 3; width: 100%; }
}


/* ---------- Going (profile) ---------- */
.go-head { margin: 18px 0 10px; font-size: 11px; font-weight: 700; letter-spacing: .08em;
           text-transform: uppercase; color: var(--text-faint); }
/* Past rows recede but stay legible. They are the record of what you actually went to, which
   is the half of this list that gains value over time rather than losing it. */
.go-row.is-past { opacity: .72; }
.go-thumb { width: 48px; height: 60px; }
.go-state { display: inline-block; margin-top: 4px; font-size: 11px; font-weight: 600;
            color: var(--text-muted); }
.go-in  { color: var(--success, #2e7d52); }
/* A cancelled event a student signed up for is the one thing on this page they must not
   scroll past, because nothing else will tell them. */
.go-off { color: var(--danger, #c0392b); }


/* ---------- Ratings ---------- */
/* The single contextual slot above the first date group. One card, never a stack — a column
   of things to do is a column people scroll past. */
.ev-ask { padding: 16px; margin-bottom: 16px; border-radius: 14px;
          background: var(--brand-pale, #eef3ff); border: 1px solid var(--brand-light, #cfdcf5); }
.ev-ask-q { font-size: 15px; font-weight: 600; color: var(--text); margin-bottom: 8px; }
.ev-ask-note { font-size: 12px; color: var(--text-muted); margin-top: 6px; }

.ev-stars { display: inline-flex; gap: 2px; }
/* Big enough to hit with a thumb without aiming. A five-star row of small targets is the
   commonest way a rating gets the wrong number. */
.ev-star-btn { width: 34px; height: 34px; border: 0; background: none; cursor: pointer;
               font-size: 22px; line-height: 1; color: var(--text-faint); padding: 0; }
.ev-star-btn:hover, .ev-star-btn.is-on { color: #e8a317; }
/* Hovering a star lights every star before it, which is what makes a row of stars read as a
   scale rather than as five separate buttons. */
.ev-stars:hover .ev-star-btn { color: #e8a317; }
.ev-stars .ev-star-btn:hover ~ .ev-star-btn { color: var(--text-faint); }

.go-rate { display: flex; align-items: center; gap: 8px; margin-top: 4px; }
.go-rate-q { font-size: 11px; font-weight: 600; color: var(--text-muted); }
.go-rate .ev-star-btn { width: 26px; height: 26px; font-size: 17px; }
.go-rated { color: #e8a317; }
.go-shut { color: var(--text-faint); font-weight: 500; }

/* ---------- Recap (console) ---------- */
.oc-recap-sum { display: flex; align-items: baseline; gap: 10px; margin-bottom: 4px; }
.oc-recap-n { font-family: 'DM Serif Display', Georgia, serif; font-size: 32px; line-height: 1;
              color: var(--brand, #3B5BA5); }
.oc-recap-lab { font-size: 13px; color: var(--text-muted); }
.oc-recap-c { padding: 10px 12px; margin-bottom: 6px; border-radius: 10px;
              background: var(--bg-alt); font-size: 13px; line-height: 1.6;
              color: var(--text); }


/* ---------- Events search ---------- */
.ev-search-btn { margin-left: auto; width: 40px; height: 40px; border-radius: 50%; flex: none;
                 border: 1px solid var(--border); background: var(--bg); color: var(--text);
                 cursor: pointer; display: inline-flex; align-items: center;
                 justify-content: center; }
.ev-search-btn:hover { border-color: var(--brand, #3B5BA5); color: var(--brand, #3B5BA5); }

.evs-bar { display: flex; gap: 8px; align-items: center; margin-bottom: 18px; }
.evs-input { flex: 1; padding: 12px 14px; font-size: 15px; font-family: inherit;
             border: 1px solid var(--border); border-radius: 10px; background: var(--bg);
             color: var(--text); }
.evs-input:focus { outline: none; border-color: var(--brand, #3B5BA5); }
.evs-close { border: 0; background: none; font-family: inherit; font-size: 13px;
             color: var(--text-muted); cursor: pointer; padding: 6px; }

.evs-sec { margin-bottom: 18px; }
.evs-lab { font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
           color: var(--text-faint); margin-bottom: 8px; }

/* The clubs you follow scroll sideways rather than wrapping. On a campus feed this is the
   most-used control on the screen and it is not a search at all — it should be reachable
   with a thumb without the rest of the panel moving. */
.evs-orgs { display: flex; gap: 8px; overflow-x: auto; padding-bottom: 4px; }
.evs-org { display: flex; flex-direction: column; align-items: center; gap: 5px; flex: 0 0 66px;
           padding: 8px 4px; border: 1px solid transparent; border-radius: 12px;
           background: none; cursor: pointer; font-family: inherit; }
.evs-org img, .evs-org-blank { width: 42px; height: 42px; border-radius: 50%; object-fit: cover; }
.evs-org-blank { display: flex; align-items: center; justify-content: center;
                 background: var(--brand-pale, #eef3ff); color: var(--brand, #3B5BA5);
                 font-weight: 600; }
.evs-org-name { font-size: 11px; color: var(--text-muted); text-align: center;
                overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 62px; }
.evs-org.is-on { border-color: var(--brand, #3B5BA5); background: var(--brand-pale, #eef3ff); }
.evs-org.is-on .evs-org-name { color: var(--brand, #3B5BA5); font-weight: 600; }

.evs-chips { display: flex; flex-wrap: wrap; gap: 6px; }
/* Base shared with .sq-chip — see the shared block at the end of this file. Only the
   selected state is particular to the events search, which is the only one that filters. */
.evs-chip.is-on, .dir-chip.is-on, .oc-ev-filter.is-on { background: var(--brand, #3B5BA5); border-color: var(--brand, #3B5BA5);
                  color: #fff; }

.evs-count { margin: 20px 0 4px; font-size: 12px; color: var(--text-muted); }
/* Shape shared — see the shared block at the end of this file. */


/* ============================================================
   SEARCH
   A column, like the events feed. Results are scanned top to bottom, and a full-width row of
   text on a wide screen makes the eye travel further than the content is worth.
   ============================================================ */
#searchBody { max-width: var(--col-read); margin-inline: auto; }

.sq-bar { display: flex; align-items: center; gap: 8px; position: relative; margin-bottom: 22px; }
.sq-form { flex: 1; }
.sq-icon { position: absolute; left: 14px; top: 50%; transform: translateY(-50%);
           color: var(--text-faint); pointer-events: none; display: flex; }
.sq-input { width: 100%; padding: 13px 14px 13px 40px; font-size: 16px; font-family: inherit;
            border: 1px solid var(--border); border-radius: 12px; background: var(--bg);
            color: var(--text); }
.sq-input:focus { outline: none; border-color: var(--brand, #3B5BA5); }
.sq-x { border: 0; background: none; font-size: 22px; line-height: 1; color: var(--text-muted);
        cursor: pointer; padding: 4px 8px; }

/* Recent first and with the most room, because on a small corpus it is the control most
   likely to be the answer. The sections below it are progressively lighter rather than three
   equal blocks — the previous version gave recent searches, categories and course codes the
   same weight, so nothing led. */
.sq-sec { margin-bottom: 24px; }
.sq-sec:first-child { margin-bottom: 30px; }
.sq-lab { font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
          color: var(--text-faint); margin-bottom: 10px; }
.sq-lab-res { margin-top: 22px; }
.sq-count { font-size: 12px; color: var(--text-muted); margin-bottom: 4px; }

.sq-chips { display: flex; flex-wrap: wrap; gap: 8px; }
/* .sq-chip shares .evs-chip — see the shared block at the end of this file. */

/* A recent search is one chip carrying two actions, so the × has to be part of it rather than
   a separate control beside it — otherwise forgetting a term means aiming at a target the
   width of a character next to one the width of a word. */
.sq-recent { display: inline-flex; align-items: stretch; border: 1px solid var(--border);
             border-radius: 999px; overflow: hidden; }
.sq-recent:hover { border-color: var(--brand, #3B5BA5); }
.sq-recent-go { padding: 8px 6px 8px 14px; border: 0; background: none; cursor: pointer;
                font-family: inherit; font-size: 13px; color: var(--text); }
.sq-recent-x { padding: 8px 12px 8px 6px; border: 0; background: none; cursor: pointer;
               font-size: 15px; line-height: 1; color: var(--text-faint); }
.sq-recent-x:hover { color: var(--danger, #c0392b); }

/* The category tiles are gone. They were the biggest block on the page and every one of them
   navigated away from it; chips that narrow in place replaced them. */

/* A quiet way out, at the bottom, where a way out belongs. Text rather than a button shape,
   because leaving is not the action this page is for. */
.sq-out { display: block; width: 100%; margin-top: 26px; padding: 12px; border: 0;
          background: none; font-family: inherit; font-size: 13px; color: var(--text-muted);
          cursor: pointer; text-align: center; }
.sq-out:hover { color: var(--brand, #3B5BA5); }

/* One row shape for all three result types, so a search answer reads as one list rather than
   as three differently-shaped ones. */
/* Square, because a listing photo is not a poster. Centres its fallback icon. */
.sq-thumb { width: 52px; height: 52px; border-radius: 10px;
            display: flex; align-items: center; justify-content: center; color: var(--text-faint); }
.sq-row-price { flex: none; font-size: 14px; font-weight: 600; color: var(--text); }

/* .sq-empty shares the empty-state block at the end of this file. */


/* ============================================================
   SHARED SHAPES
   Written after three near-copies of each had accumulated across the events, org, profile and
   search surfaces. Three definitions of "a compact row" is three places for them to drift,
   and the drift shows up as one list looking subtly wrong next to another.
   ============================================================ */

/* Two column widths, and only two, each with a reason.
     --col-read  reading and browsing: the events feed, search results. Poster-first and
                 row-first content is scanned top to bottom, and a wider column makes the eye
                 travel further than the content is worth.
     --col-work  the officer console: a form beside a list, denser, and it needs the room. */
:root { --col-read: 560px; --col-work: 680px; }

/* THE compact row. A thumbnail, a title, a subtitle, sometimes a trailing value. Used by the
   org page's events, the profile's Going list and every search result — so those three lists
   are the same object wherever a student meets them. Sizes that genuinely differ (a portrait
   event poster against a square listing photo) stay with their own class. */
.op-event, .go-row, .sq-row {
  display: flex; align-items: center; gap: 12px; width: 100%; padding: 10px;
  margin-bottom: 8px; border: 1px solid var(--border); border-radius: 12px;
  background: var(--surface); cursor: pointer; font-family: inherit; text-align: left;
}
.op-event:hover, .go-row:hover, .sq-row:hover { border-color: var(--brand, #3B5BA5); }

.go-thumb, .sq-thumb {
  border-radius: 8px; flex: none; overflow: hidden; background: var(--bg-alt);
}
.go-thumb img, .sq-thumb img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}

.op-event-text, .go-text, .sq-row-text { flex: 1; min-width: 0; }
.op-event-title, .go-title, .sq-row-title {
  font-size: 14px; font-weight: 600; color: var(--text);
  /* Truncated rather than wrapped. A row whose height depends on its title makes a list of
     them ragged, and the beginning of a title is what identifies it. */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.op-event-when, .go-when { font-size: 12px; color: var(--text); margin-top: 2px; }
.op-event-where, .go-where, .sq-row-sub {
  font-size: 12px; color: var(--text-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sq-row-sub { margin-top: 2px; }

/* One chip. The events search and the search page had identical copies. */
.evs-chip, .sq-chip, .dir-chip, .oc-ev-filter {
  padding: 8px 13px; font-size: 13px; border-radius: 999px; cursor: pointer;
  font-family: inherit; border: 1px solid var(--border); background: var(--bg);
  color: var(--text);
}
.evs-chip:hover, .sq-chip:hover, .dir-chip:hover, .oc-ev-filter:hover { border-color: var(--brand, #3B5BA5); }
/* Selected. The marketplace search narrows in place now, so its chips need the same on-state
   the events search chips already had — one chip style, one selected look. */
.sq-chip.is-on { background: var(--brand, #3B5BA5); border-color: var(--brand, #3B5BA5); color: #fff; }
.sq-chip:hover { color: var(--brand, #3B5BA5); }

/* One empty state, three users. Each says something different; none of them should LOOK
   different, or "nothing here" reads as a different kind of nothing on each page. */
.ev-empty, .evs-none, .sq-empty {
  margin-top: 26px; padding: 34px 20px; text-align: center;
  border: 1px dashed var(--border); border-radius: 14px;
}
.ev-empty-title, .evs-none-t, .sq-empty-t {
  font-family: 'DM Serif Display', Georgia, serif; font-size: 20px;
  color: var(--text); margin-bottom: 8px;
}
.ev-empty p, .evs-none p, .sq-empty p {
  font-size: 13px; color: var(--text-muted); line-height: 1.7; margin: 0 0 16px;
}


/* ---------- Save ---------- */
/* One control for listings, books and events, and the same mark on the profile's Saved tab.
   Outline until saved, filled after — the shape carries the state, so nothing depends on
   colour alone.
   The BUTTON stays 34px because that is a thumb; the ICON inside is 18px. Separating the two
   is what lets the target be big while the mark stays quiet — the old star was one 19px glyph
   doing both jobs and doing the second one loudly.
   Brand rather than red: red belongs to a heart and to destructive actions, and this is
   neither. Amber is spoken for by the rating stars. */
.fav-btn { width: 34px; height: 34px; flex: none; border: 0; background: none;
            cursor: pointer; padding: 0; border-radius: 50%;
            display: inline-flex; align-items: center; justify-content: center;
            color: var(--text-faint); transition: color .14s ease, transform .14s ease; }
.fav-btn svg { width: 18px; height: 18px; display: block; }
.fav-btn:hover { color: var(--brand, #3B5BA5); }
.fav-btn.is-on { color: var(--brand, #3B5BA5); }
/* A small kick on the tap that sets it. It fires on the class change, so it happens on the
   optimistic paint — the moment the student pressed, not when the network agrees. */
.fav-btn.is-on svg { animation: fav-pop .22s ease; }
@keyframes fav-pop { 0% { transform: scale(.8); } 60% { transform: scale(1.15); } 100% { transform: scale(1); } }
/* On a listing card it sits in the footer beside the price, so it is pushed to the right of
   the price and left of the message button. */
.lc-empty{grid-column:1 / -1;text-align:center;padding:60px 20px;color:var(--text-faint);}



/* ---------- A doorway icon in a page header ---------- */
/* Square, quiet, and sized to the button it stands beside so the pair reads as one group
   rather than as a control and an afterthought. */
/* Wrapped rather than restyling .page-header from a distance. The header is already
   space-between with two children; a third would have spread the three apart instead of
   keeping the actions together, and overriding a rule 1900 lines above is how two
   definitions of one layout start. */
.page-header-actions { display: flex; align-items: center; gap: 8px; flex: none; }

/* ---------- Search: tools row ---------- */
.sq-tools { display: flex; align-items: center; gap: 8px; margin: 0 0 14px; }
.sq-tools .filters-btn { margin-right: auto; }

/* Two states of one control, so they share a border and read as a switch rather than as two
   buttons that happen to sit together. */
.sq-view { display: inline-flex; border: 1px solid var(--border); border-radius: 9px;
           overflow: hidden; flex: none; }
.sq-view button { display: flex; align-items: center; justify-content: center;
                  width: 36px; height: 32px; border: 0; background: var(--bg);
                  color: var(--text-faint); cursor: pointer; }
.sq-view button + button { border-left: 1px solid var(--border); }
.sq-view button.is-on { background: var(--brand, #3B5BA5); color: #fff; }

/* ---------- Search: grid results ---------- */
/* auto-fill with a minimum, not a fixed count: the same rule works at 390px and at 1400px
   without a media query per breakpoint, and the tile decides how many fit. */
.sq-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
           gap: 18px 12px; margin-bottom: 6px; }

/* BORDERLESS. Every tile used to be a bordered box containing an image that already reads as
   a rectangle — two nested outlines each, nine of them on a screen, and the page became a
   grid of boxes rather than a grid of things. The image is the tile now and the text sits
   under it, which is what every shopping grid worth copying does. */
.sq-tile-card { position: relative; cursor: pointer; }
.sq-tile-img { position: relative; aspect-ratio: 1; border-radius: 12px; overflow: hidden;
               display: flex; align-items: center; justify-content: center;
               background: var(--bg-alt); transition: transform .16s ease; }
.sq-tile-card:hover .sq-tile-img { transform: translateY(-2px); }
.sq-tile-img img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* A photoless tile was a 26px glyph marooned on a pale square. Large and faint, the icon
   reads as a texture on a coloured ground instead — the tile looks intentional rather than
   like an image that failed to load. */
.sq-tile-glyph { opacity: .3; display: flex; }

/* Smaller here than elsewhere: .fav-btn is 36px, which on a 175px tile is a fifth of its
   width wearing a white disc. */
.sq-tile-fav { position: absolute; top: 5px; right: 5px; width: 28px; height: 28px;
                background: rgba(255,255,255,.82); backdrop-filter: blur(3px); }
.sq-tile-fav svg { width: 15px; height: 15px; }
.sq-tile-fav:hover { background: #fff; }

.sq-tile-body { padding: 9px 2px 0; }
/* What the thing IS, above what it is called. The list view carries this as a subtitle and
   the grid had dropped it, so a textbook and a desk looked the same. */
.sq-tile-kicker { font-size: 10px; font-weight: 700; letter-spacing: .07em;
                  text-transform: uppercase; color: var(--text-faint); margin-bottom: 3px; }
/* TWO LINES ALWAYS, whether the title needs them or not. This is the single change that makes
   a grid look composed: without it a one-line title and a two-line title put their prices at
   different heights, and a row of prices that do not line up is the most visible way a grid
   looks unfinished. */
.sq-tile-title { font-size: 13px; font-weight: 600; color: var(--text); line-height: 1.35;
                 min-height: 2.7em;
                 display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
                 overflow: hidden; }
/* In the text colour and a size up. In a marketplace the price is the second thing read and
   it was set in muted grey at the same size as the title, so it read as an afterthought.
   The em dash holds the line when there is no price, so the baseline survives. */
.sq-tile-price { margin-top: 3px; font-size: 14px; font-weight: 700; color: var(--text); }

/* ---------- The category picker inside the filter drawer ---------- */
.df-static { cursor: default; }


/* ============================================================
   PROFILE
   The shape Instagram and TikTok settled on, adopted for the reason they did: the header is
   an INTRODUCTION and the tabs are the page. Everything a student came here to do — their
   listings, their saved things, what they are going to — is one tap away instead of one
   scroll past their own inventory.
   ============================================================ */

/* Avatar beside the name, everything a step smaller than it was — the header introduces
   the page, and the smaller it is, the more of what a student came to do is on screen. */
.pf-head { display: flex; align-items: center; gap: 14px; margin-bottom: 12px; }
.pf-avatar-wrap { position: relative; flex: none; }
/* The edit mark sits on the avatar's lower-right edge. Visually 25px; the ::before extends the
   hit area to 45px, over the --tap-min floor, without making the mark itself any bigger. The
   cream ring separates it from the avatar and from a photo of any colour. */
.pf-edit-badge { position: absolute; right: -2px; bottom: -2px; width: 25px; height: 25px;
                 border-radius: 50%; border: 0; padding: 0; cursor: pointer;
                 background: var(--brand); color: #fff; box-shadow: 0 0 0 2.5px var(--bg);
                 display: flex; align-items: center; justify-content: center; }
.pf-edit-badge::before { content: ''; position: absolute; inset: -10px; border-radius: 50%; }
.pf-edit-badge:hover { background: var(--brand-mid); }
.pf-edit-badge:focus-visible { outline: var(--focus-ring); outline-offset: 3px; }
.pf-id { flex: 1; min-width: 0; }
.pf-name { font-family: 'DM Serif Display', Georgia, serif; font-size: 21px; line-height: 1.15;
           color: var(--text); }
.pf-handle-row { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; margin-top: 3px; }
.pf-handle { min-width: 0; }
.pf-at { font-size: 13px; color: var(--brand); font-weight: 500; }
.pf-pick { font-size: 13px; color: var(--brand); font-weight: 500; background: none; border: 0;
           padding: 0; cursor: pointer; font-family: inherit; }
.pf-pick:hover { text-decoration: underline; }
.pf-edu { margin-top: 0; font-size: 10.5px; padding: 2px 8px; gap: 4px; }
.pf-edu svg { width: 11px; height: 11px; }
/* Context, not content. One muted line where a labelled four-cell grid used to give each of
   major, year and join date the weight of a section heading. */
.pf-meta { margin-top: 3px; font-size: 12px; color: var(--text-muted); }
.pf-bio { margin-top: 12px; font-size: 13.5px; line-height: 1.55; color: var(--text);
          white-space: pre-wrap; }
.pf-email { margin-top: 2px; font-size: 12px; color: var(--text-muted); }

/* COUNTS — no box, light rules between the columns. The active tab's number turns green so
   the counts and the tab bar agree about where you are. */
.pf-counts { display: flex; margin: 16px 0 4px; }
.pf-count { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 1px;
            background: none; border: 0; border-right: 1px solid var(--border); padding: 2px 0;
            cursor: pointer; font-family: inherit; }
.pf-count:last-child { border-right: 0; }
.pf-count-n { font-family: 'DM Serif Display', Georgia, serif; font-size: 19px; line-height: 1.15;
              color: var(--text); }
.pf-count-l { font-size: 11px; color: var(--text-muted); }
.pf-count.is-on .pf-count-n { color: var(--brand); }
.pf-count:focus-visible { outline: var(--focus-ring); outline-offset: 2px; border-radius: 6px; }

/* Sticky, because that is what makes a long Saved list navigable rather than somewhere to get
   lost. Full-bleed border under the row so the tabs read as a divider between the
   introduction and the content, which is the job they are doing. */
.pf-tabs { display: flex; position: sticky; top: 0; z-index: 3; margin-top: 14px;
           background: var(--bg); border-bottom: 1px solid var(--border); }
.pf-tab { flex: 1; display: flex; align-items: center; justify-content: center; gap: 7px;
          padding: 13px 6px; border: 0; background: none; cursor: pointer; font-family: inherit;
          font-size: 13px; font-weight: 600; color: var(--text-faint);
          border-bottom: 2px solid transparent; margin-bottom: -1px; }
.pf-tab:hover { color: var(--text-muted); }
/* The underline is the whole indicator. A filled tab would compete with the content below it,
   and the content is the point. */
.pf-tab.is-on { color: var(--text); border-bottom-color: var(--text); }
.pf-pane { padding-top: 18px; }

/* The label goes on a phone and the icon carries it. Three words at 390px either wrap or
   shrink below reading size, and an icon that has been sitting beside its word all along is
   already learned by the time the word disappears. */
@media (max-width: 480px) {
  .pf-tab span { display: none; }
  .pf-tab { padding: 14px 6px; }
  .pf-head { gap: 14px; }
  .pf-name { font-size: 20px; }
}


/* ============================================================
   HOME FEED  (js/feed.js)
   The Home tab's own page. Sections are drawn only when they have something in
   them, so these rules never have to style an empty state.
============================================================ */
.feed-hello{margin-bottom:var(--sp-5);}
.feed-greet{font-family:'DM Serif Display',serif;font-size:var(--fs-display);line-height:1.1;margin-bottom:var(--sp-1);}
.feed-sub{font-size:var(--fs-small);color:var(--text-muted);}
.feed-sec{margin-bottom:var(--sp-6);}
.feed-head{display:flex;align-items:baseline;justify-content:space-between;gap:var(--sp-3);margin-bottom:var(--sp-3);}
/* Same green as .mk-sec-title on the marketplace. These headings were inheriting --text,
   so "Happening this week" and "New in marketplace" sat in near-black while "Featured" one
   tap away was green — the same kind of heading in two colours. */
.feed-title{font-family:'DM Serif Display',serif;font-size:var(--fs-title);color:var(--brand);}
.feed-more{display:inline-flex;align-items:center;gap:4px;background:none;border:none;cursor:pointer;
  font-family:'DM Sans',sans-serif;font-size:var(--fs-small);font-weight:600;color:var(--brand);padding:0;}
.feed-more:hover{color:var(--brand-mid);}
/* Same card grid as the marketplace; on the feed it is a short preview, so it is pinned to
   two columns at every width rather than widening to three. */
.feed-grid{grid-template-columns:repeat(2,minmax(0,1fr));}
/* A sideways strip, not a stack. Tall portrait posters in a column beside the marketplace's
   2-up grid of short landscape cards made the feed look like two pages stitched together.
   The Events page keeps the poster treatment — a flyer is a portrait thing and that is the
   page you go to in order to look at them. */
.feed-events{display:flex;gap:var(--sp-3);overflow-x:auto;scrollbar-width:none;padding:2px 0;}
.feed-events::-webkit-scrollbar{display:none;}
.fev-card{flex:0 0 214px;max-width:214px;background:var(--surface);border:1px solid var(--border);
  border-radius:var(--radius-lg);overflow:hidden;box-shadow:var(--shadow-sm);cursor:pointer;
  padding:0;text-align:left;font-family:inherit;display:block;
  transition:transform var(--dur) var(--ease),box-shadow var(--dur) var(--ease);}
.fev-card:hover{transform:translateY(-2px);box-shadow:var(--shadow-md);}
.fev-card:focus-visible{outline:var(--focus-ring);outline-offset:var(--focus-offset);}
.fev-banner{height:76px;position:relative;background:var(--bg-alt);}
.fev-img{width:100%;height:100%;object-fit:cover;display:block;}
/* The date block reads as a torn-off calendar square sitting on the banner, which is why it
   keeps a solid ground of its own rather than being written straight onto the artwork. */
.fev-date{position:absolute;top:9px;left:9px;width:40px;background:var(--surface);
  border-radius:9px;padding:4px 0 5px;box-shadow:var(--shadow-xs);
  display:flex;flex-direction:column;align-items:center;}
.fev-dow{font-size:9px;font-weight:700;letter-spacing:.08em;color:var(--accent-warm);}
.fev-day{font-family:'DM Serif Display',serif;font-size:17px;color:var(--accent-warm);line-height:1.05;}
.fev-body{padding:9px 12px 11px;display:flex;flex-direction:column;align-items:flex-start;gap:5px;}
/* Clamped so a long title cannot make one card taller than the rest of the row. */
.fev-title{font-size:14px;font-weight:600;color:var(--text);line-height:1.25;align-self:stretch;
  display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;}
.fev-when{font-size:11.5px;color:var(--text-muted);align-self:stretch;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.fev-type,.ev-type{background:var(--brand-pale);color:var(--brand-mid);font-size:10.5px;font-weight:600;
  padding:3px 9px;border-radius:var(--radius-pill);}
.feed-clubs{width:100%;display:flex;align-items:center;gap:var(--sp-3);text-align:left;cursor:pointer;
  background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:var(--sp-3) var(--sp-4);
  font-family:'DM Sans',sans-serif;}
.feed-clubs:hover{border-color:var(--brand-light);}
.feed-clubs-icon{flex:none;width:40px;height:40px;border-radius:var(--radius-sm);background:var(--brand-pale);
  color:var(--brand);display:flex;align-items:center;justify-content:center;}
.feed-clubs-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px;}
.feed-clubs-title{font-size:var(--fs-small);font-weight:600;color:var(--text);}
.feed-clubs-sub{font-size:var(--fs-micro);color:var(--text-muted);line-height:1.45;text-transform:none;letter-spacing:0;}
.feed-clubs svg{flex:none;color:var(--text-faint);}
@media(max-width:768px){
  .feed-grid{grid-template-columns:repeat(2,minmax(0,1fr));}
}

/* ============================================================
   ACCESSIBILITY (Phase 8.1)
   ============================================================ */
/* These four removed the browser's focus outline and put nothing in its place, so a keyboard user
   could not see when they were on a select, a search box, the admin's search bars or a colour
   swatch. Two classes each, to outrank the outline:none in their own rules. The swatch uses
   :focus-within because its input is translated and clipped inside the swatch box. */
.form-select:focus-visible,
.search-input:focus-visible,
.a-sbar:focus-visible{outline:var(--focus-ring);outline-offset:var(--focus-offset);}
.c-swatch:focus-within{outline:var(--focus-ring);outline-offset:2px;}

/* Anyone who has asked their device for less motion gets it. The app declares 71 transitions and
   7 animations; until now only the skeleton shimmer listened. .01ms rather than 0 so that
   anything waiting on a transitionend event still receives one. */
@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{animation-duration:.01ms!important;animation-iteration-count:1!important;
    transition-duration:.01ms!important;scroll-behavior:auto!important;}
}
