/* The Uni Deals shared site system. Built only from tokens.css and local fonts. */

/* ---------- Base ---------- */

*, *::before, *::after { box-sizing: border-box; }

:root {
  color-scheme: light;
  --site-accent-text: var(--tud-color-accent-text-on-light);
  --site-content: 1120px;
  --site-reading: 720px;
  /* tokens.css is generated, so the bundled web face is prepended here rather
     than edited into it. The nastaliq faces stay as a local fallback. */
  --site-font-urdu: 'Noto Sans Arabic', var(--tud-font-urdu);
  /* The edge of something you can type in, which WCAG 1.4.11 holds to 3:1
     against whatever sits behind it. --tud-color-border-strong is a decorative
     rule colour and measures 1.35:1 on white, so controls get their own token
     rather than borrowing that one. 3.43:1 on #FFFFFF, 3.18:1 on the canvas. */
  --site-control-border: #96897F;
}

/* data-theme is the only thing that decides the theme. Every page sets it to
   light or dark before first paint, so the operating system's own dark mode
   never reaches the page: dark is only ever reached through the toggle. */
[data-theme="dark"] {
  color-scheme: dark;
  --site-accent-text: var(--tud-color-accent-text-on-dark);
  /* 3.38:1 on the surface a field sits on, 3.09:1 on the raised surface. */
  --site-control-border: #726C64;
}

html {
  min-width: 320px;
  scroll-padding-top: 88px;
}

body {
  min-width: 320px;
  margin: 0;
  overflow-x: hidden;
  font-family: var(--tud-font-primary);
  font-size: 16px;
  font-weight: 500;
  line-height: 1.55;
  color: var(--tud-color-text-primary);
  background: var(--tud-color-bg-canvas);
  -webkit-font-smoothing: antialiased;
}

main { display: block; }
img, svg { display: block; }
img { max-width: 100%; height: auto; }
h1, h2, h3 { margin: 0; font-weight: 800; letter-spacing: -0.025em; }
p { margin: 0; }
button, input, select, textarea { font: inherit; }
a { color: var(--site-accent-text); text-underline-offset: 0.18em; }
a:hover { text-decoration-thickness: 2px; }

/* Any Urdu on the site, wherever it appears. The @font-face carries an
   Arabic-only unicode-range, so a page with no Urdu never fetches the file. */
[lang="ur"] { font-family: var(--site-font-urdu); }

:where(a, button, input, select, textarea, summary):focus-visible {
  outline: 3px solid var(--tud-color-border-focus);
  outline-offset: 3px;
}

::selection {
  color: var(--tud-color-text-on-accent);
  background: var(--tud-color-accent-base);
}

.hidden { display: none !important; }
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: fixed;
  top: var(--tud-space-3);
  left: var(--tud-space-3);
  z-index: 100;
  padding: var(--tud-space-3) var(--tud-space-4);
  border-radius: var(--tud-radius-lg);
  color: var(--tud-color-text-on-accent);
  background: var(--tud-color-accent-base);
  font-weight: 800;
  text-decoration: none;
  transform: translateY(-160%);
}

.skip-link:focus { transform: translateY(0); }

/* ---------- Shared layout and type ---------- */

.container {
  width: min(100% - (var(--tud-space-6) * 2), var(--site-content));
  margin-inline: auto;
}

.page-shell {
  min-height: 54vh;
  padding-block: clamp(var(--tud-space-10), 7vw, 88px) clamp(var(--tud-space-16), 9vw, 112px);
}

.page-shell--centered {
  display: grid;
  place-items: center;
}

.section-block { padding-block: clamp(var(--tud-space-16), 8vw, 104px); }
.section-band {
  background: var(--tud-color-bg-sunken);
  border-block: var(--tud-border-hairline) solid var(--tud-color-border-default);
}

.overline,
.pill,
.eyebrow {
  font-family: var(--tud-font-secondary);
  font-size: 12px;
  font-weight: 700;
  line-height: 16px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.overline,
.eyebrow { color: var(--site-accent-text); }
.overline { margin-bottom: var(--tud-space-4); }

.pill {
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  padding: var(--tud-space-2) var(--tud-space-3);
  border: var(--tud-border-hairline) solid var(--tud-color-accent-tint-border);
  border-radius: var(--tud-radius-pill);
  color: var(--site-accent-text);
  background: var(--tud-color-accent-tint);
  letter-spacing: 0.1em;
}

.display-title {
  max-width: 13ch;
  font-size: clamp(42px, 6.2vw, 76px);
  line-height: 0.98;
  letter-spacing: -0.045em;
}

.page-title {
  font-size: clamp(36px, 5vw, 56px);
  line-height: 1.02;
  letter-spacing: -0.04em;
}

.section-title {
  font-size: clamp(30px, 4vw, 44px);
  line-height: 1.08;
  letter-spacing: -0.035em;
}

.lede {
  max-width: 50ch;
  color: var(--tud-color-text-secondary);
  font-size: clamp(17px, 2vw, 19px);
  line-height: 1.55;
}

.fine {
  color: var(--tud-color-text-muted);
  font-size: 13px;
  line-height: 1.45;
}

.numeric {
  font-family: var(--tud-font-secondary);
  font-variant-numeric: tabular-nums;
}

.section-heading {
  display: flex;
  align-items: end;
  justify-content: space-between;
  gap: var(--tud-space-8);
  margin-bottom: var(--tud-space-10);
}

.section-heading > p {
  max-width: 48ch;
  color: var(--tud-color-text-secondary);
}

/* ---------- Shared surfaces ---------- */

.card {
  border: var(--tud-border-default) solid var(--tud-color-border-default);
  border-radius: var(--tud-radius-lg);
  background: var(--tud-color-bg-surface);
  box-shadow: var(--tud-elevation-e1);
}

.card--hero { border-radius: var(--tud-radius-xl); }
.card--sunken { background: var(--tud-color-bg-sunken); }

.status-icon {
  display: inline-grid;
  width: 48px;
  height: 48px;
  place-items: center;
  border-radius: var(--tud-radius-pill);
  color: var(--tud-color-text-inverse);
  background: var(--tud-color-bg-inverse);
}

/* ---------- Buttons ---------- */

.btn {
  display: inline-flex;
  min-height: 52px;
  align-items: center;
  justify-content: center;
  gap: var(--tud-space-2);
  padding: var(--tud-space-3) var(--tud-space-6);
  border: var(--tud-border-default) solid transparent;
  border-radius: var(--tud-radius-lg);
  font-family: var(--tud-font-primary);
  font-size: 16px;
  font-weight: 800;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition:
    transform var(--tud-duration-fast) var(--tud-ease-standard),
    background-color var(--tud-duration-fast) var(--tud-ease-standard),
    border-color var(--tud-duration-fast) var(--tud-ease-standard),
    box-shadow var(--tud-duration-fast) var(--tud-ease-standard);
}

.btn:hover { text-decoration: none; transform: translateY(-2px); }
/* Press goes below the resting line, not back to it: the button should feel
   pushed into the page, which a return to 0 never reads as. */
.btn:active { transform: translateY(1px) scale(0.99); }
.btn:disabled { cursor: wait; opacity: 0.62; transform: none; }

.btn-primary {
  color: var(--tud-color-text-on-accent);
  background: var(--tud-color-accent-base);
}

.btn-primary:hover { background: var(--tud-color-ramp-accent-550); box-shadow: var(--tud-elevation-e2); }
.btn-primary:active { background: var(--tud-color-accent-pressed); }

.btn-secondary {
  color: var(--tud-color-text-primary);
  background: var(--tud-color-bg-surface);
  border-color: var(--tud-color-border-strong);
}

.btn-secondary:hover { background: var(--tud-color-bg-sunken); border-color: var(--tud-color-text-muted); }
.btn-sm { min-height: 44px; padding: var(--tud-space-2) var(--tud-space-4); font-size: 14px; }
.btn-block { width: 100%; }

/* ---------- Forms ---------- */

.form-stack { display: grid; gap: var(--tud-space-5); }
.field { display: grid; gap: var(--tud-space-2); min-width: 0; }
.field-row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--tud-space-4); }

.field-label {
  color: var(--tud-color-text-secondary);
  font-size: 14px;
  font-weight: 800;
  line-height: 1.35;
}

/* Required marker. aria-hidden in the markup on purpose: the input already
   carries `required`, which every screen reader announces, so exposing the
   glyph too would read the field as "Email star required". This is the sighted
   reader's cue only. A field without one is optional, which is why the two
   "(optional)" labels on the partners form were removed: marking both sides
   makes an unmarked field ambiguous. */
.req {
  /* A utility mark, not a brand moment. In accent at the label's own 800 it
     competed with the button for the one orange the region is allowed, and the
     asterisk glyph sits high enough in Outfit to read as a floating blob rather
     than part of the word. Muted, lighter, a size down, and nudged onto the
     x-height instead of hanging above it. */
  display: inline-block;
  margin-left: 3px;
  transform: translateY(0.18em);
  color: var(--tud-color-text-muted);
  font-size: 0.9em;
  font-weight: 600;
  line-height: 1;
}

.field-hint { color: var(--tud-color-text-muted); font-size: 13px; }

.field-control,
input,
select,
textarea,
.select-trigger {
  width: 100%;
  min-height: 52px;
  padding: 13px var(--tud-space-4);
  border: var(--tud-border-default) solid var(--site-control-border);
  border-radius: var(--tud-radius-lg);
  color: var(--tud-color-text-primary);
  background: var(--tud-color-bg-surface);
  font-size: 16px;
  font-weight: 500;
  line-height: 1.4;
  transition:
    border-color var(--tud-duration-fast) var(--tud-ease-standard),
    box-shadow var(--tud-duration-fast) var(--tud-ease-standard),
    background-color var(--tud-duration-fast) var(--tud-ease-standard);
}

textarea { min-height: 112px; resize: vertical; }
input::placeholder, textarea::placeholder { color: var(--tud-color-text-muted); opacity: 1; }
input[readonly] { color: var(--tud-color-text-primary); background: var(--tud-color-bg-sunken); }

input:hover,
select:hover,
textarea:hover,
.select-trigger:hover { border-color: var(--tud-color-text-primary); }

/* The site's focus indicator is a 3px solid ring at a 3px offset, and a field
   is the last control that should carry a weaker one than a link does. The
   glow stays as an addition, sitting in the gap between the border and the
   ring so the two read as one thickening edge rather than two rings. */
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
.select-trigger:focus-visible {
  outline: 3px solid var(--tud-color-border-focus);
  outline-offset: 3px;
  border-color: var(--tud-color-border-focus);
  box-shadow: var(--tud-focus-ring);
}

select { appearance: none; padding-right: 48px; cursor: pointer; }
.select-wrap { position: relative; display: grid; min-width: 0; }

/* ---------- Custom select ----------
   Progressive enhancement: the native <select> stays in the DOM as the form
   value and keeps native required-validation. JS layers a branded listbox on
   top. With JS off, the native control is untouched and still works. */

.select-wrap[data-tud-select] .select-native {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  pointer-events: none;
}

.select-trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--tud-space-3);
  padding-right: var(--tud-space-4);
  text-align: left;
  cursor: pointer;
}

.select-trigger[data-placeholder="true"] .select-trigger-label { color: var(--tud-color-text-muted); }
.select-trigger .select-chevron { position: static; flex: none; transform: none; transition: transform var(--tud-duration-fast) var(--tud-ease-standard); }
.select-trigger[aria-expanded="true"] .select-chevron { transform: rotate(180deg); }
.select-trigger[aria-expanded="true"] { border-color: var(--tud-color-border-focus); }
.select-trigger-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.select-popover {
  position: absolute;
  z-index: 40;
  top: calc(100% + var(--tud-space-2));
  right: 0;
  left: 0;
  max-height: 292px;
  padding: var(--tud-space-2);
  overflow-y: auto;
  border: var(--tud-border-default) solid var(--tud-color-border-strong);
  border-radius: var(--tud-radius-lg);
  background: var(--tud-color-bg-surface);
  box-shadow: var(--tud-elevation-e3);
}

.select-popover[data-place="top"] { top: auto; bottom: calc(100% + var(--tud-space-2)); }
.select-listbox { margin: 0; padding: 0; list-style: none; }
.select-listbox:focus-visible { outline: none; }

.select-option {
  display: flex;
  min-height: 44px;
  align-items: center;
  justify-content: space-between;
  gap: var(--tud-space-3);
  padding: var(--tud-space-2) var(--tud-space-3);
  border-radius: var(--tud-radius-md);
  font-size: 16px;
  font-weight: 600;
  cursor: pointer;
}

.select-option[data-active="true"] { background: var(--tud-color-bg-sunken); }
.select-option[aria-selected="true"] { color: var(--site-accent-text); font-weight: 800; }
.select-option-tick { flex: none; opacity: 0; }
.select-option[aria-selected="true"] .select-option-tick { opacity: 1; }
.select-chevron {
  position: absolute;
  top: 50%;
  right: var(--tud-space-4);
  color: var(--tud-color-text-muted);
  pointer-events: none;
  transform: translateY(-50%);
}

.form-msg {
  min-height: 0;
  color: var(--tud-color-status-error-text);
  font-size: 14px;
  font-weight: 700;
}

.form-msg:not(:empty) {
  padding: var(--tud-space-3) var(--tud-space-4);
  border-radius: var(--tud-radius-lg);
  background: var(--tud-color-status-error-bg);
}

/* ---------- Site navigation ---------- */

.site-header {
  position: relative;
  z-index: 20;
  border-bottom: var(--tud-border-hairline) solid var(--tud-color-border-default);
  background: var(--tud-color-bg-canvas);
}

.nav {
  width: min(100% - (var(--tud-space-6) * 2), var(--site-content));
  min-height: 80px;
  margin-inline: auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--tud-space-6);
}

.nav-brand { min-height: 44px; flex: 0 0 auto; display: inline-flex; align-items: center; border-radius: var(--tud-radius-xs); }
/* Two-image theme swap: both variants ship in the markup and CSS hides the one
   that does not match data-theme. Keyed on the attribute alone, so a logo or a
   drawing follows the toggle rather than the operating system. */
.theme-logo { display: inline-grid; place-items: center start; line-height: 0; }
.theme-logo > img { grid-area: 1 / 1; }
:root:not([data-theme="dark"]) .theme-logo > .theme-logo-dark { display: none; }
[data-theme="dark"] .theme-logo > .theme-logo-light { display: none; }

/* The drawings run the same swap. Hiding is all these rules do, so the variant
   on show keeps whatever display its own layout gives it and the ink and orange
   versions of a drawing sit in exactly the same place. */
:root:not([data-theme="dark"]) .theme-art > .theme-art-dark { display: none; }
[data-theme="dark"] .theme-art > .theme-art-light { display: none; }
.nav-logo { width: auto; height: 31px; }
.nav-right, .nav-links { display: flex; align-items: center; }
.nav-right { gap: var(--tud-space-4); }
.nav-links { gap: var(--tud-space-6); }

/* Merchant pages put the theme toggle last in the DOM so a preference control
   stops tabbing ahead of the page's only action. Tab order follows the DOM, so
   `order` is free to put it back between the links and the button. */
.nav-right--merchant .nav-links { order: 1; }
.nav-right--merchant .theme-toggle { order: 2; }
.nav-right--merchant .nav-cta { order: 3; }

.nav-link {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  color: var(--tud-color-text-secondary);
  font-size: 15px;
  font-weight: 700;
  text-decoration: none;
}

.nav-link:hover { color: var(--tud-color-text-primary); }

.theme-toggle {
  display: inline-grid;
  width: 44px;
  height: 44px;
  flex: 0 0 auto;
  padding: 0;
  place-items: center;
  border: var(--tud-border-default) solid var(--tud-color-border-strong);
  border-radius: var(--tud-radius-pill);
  color: var(--tud-color-text-primary);
  background: var(--tud-color-bg-surface);
  cursor: pointer;
  transition:
    transform var(--tud-duration-fast) var(--tud-ease-standard),
    background-color var(--tud-duration-fast) var(--tud-ease-standard);
}

.theme-toggle:hover { background: var(--tud-color-bg-sunken); transform: rotate(7deg); }
.theme-toggle:active { transform: scale(0.96); }
.icon-sun { display: none; }
[data-theme="dark"] .icon-sun { display: block; }
[data-theme="dark"] .icon-moon { display: none; }

/* ---------- Signed-in account menu ---------- */

/* Built by app.js in place of the nav CTA once /v1/me confirms the session.
   The button keeps the CTA's own btn classes, so the nav's layout does not
   move by a pixel when the link becomes a disclosure; the chevron is the only
   visible difference. The popover borrows the branded select's clothes
   (border-strong, radius-lg, e3) because it is the same object: a small
   surface that opens under a control and closes when the answer is picked. */
.nav-account { position: relative; }
.nav-account-btn { gap: var(--tud-space-1); }
.nav-account-chevron {
  flex: none;
  transition: transform var(--tud-duration-fast) var(--tud-ease-standard);
}
.nav-account-btn[aria-expanded="true"] .nav-account-chevron { transform: rotate(180deg); }

.nav-account-menu {
  position: absolute;
  z-index: 40;
  top: calc(100% + var(--tud-space-2));
  right: 0;
  min-width: 248px;
  padding: var(--tud-space-2);
  border: var(--tud-border-default) solid var(--tud-color-border-strong);
  border-radius: var(--tud-radius-lg);
  background: var(--tud-color-bg-surface);
  box-shadow: var(--tud-elevation-e3);
}

/* Whose session, and where it stands. The email is recognized, not read, so it
   takes the label face; the status line carries the one colored word in the
   menu, in the same status colors every other surface uses. */
.nav-account-head {
  display: flex;
  align-items: center;
  gap: var(--tud-space-3);
  padding: var(--tud-space-2) var(--tud-space-3) var(--tud-space-3);
  border-bottom: var(--tud-border-hairline) solid var(--tud-color-border-default);
  margin-bottom: var(--tud-space-2);
}
.nav-account-identity { display: block; min-width: 0; }
.nav-account-nickname {
  display: block;
  color: var(--tud-color-text-primary);
  font-family: var(--tud-font-primary);
  font-size: 15px;
}
.nav-account-avatar {
  display: grid;
  flex: none;
  width: 48px;
  height: 48px;
  place-items: center;
  overflow: hidden;
  border: 2px solid var(--tud-color-text-primary);
  border-radius: var(--tud-radius-pill);
  background: var(--tud-color-deal-panel-0);
}
.nav-account-avatar[data-profile-color="chai-cream"] { background: var(--tud-color-deal-panel-1); }
.nav-account-avatar[data-profile-color="rickshaw-teal"] { background: var(--tud-color-deal-panel-2); }
.nav-account-avatar[data-profile-color="kite-pink"] { background: var(--tud-color-deal-panel-3); }
.nav-account-avatar[data-profile-color="mehndi-green"] { background: var(--tud-color-deal-panel-4); }
.nav-account-avatar[data-profile-color="mailbox-blue"] { background: var(--tud-color-deal-panel-5); }
.nav-account-avatar img { width: 42px; height: 42px; object-fit: contain; }
[data-theme="dark"] .nav-account-avatar img { filter: invert(47%) sepia(96%) saturate(2858%) hue-rotate(345deg) brightness(105%) contrast(101%); }
.nav-account-email {
  display: block;
  color: var(--tud-color-text-primary);
  font-family: var(--tud-font-secondary);
  font-size: 14px;
  font-weight: 700;
  overflow-wrap: anywhere;
}
.nav-account-status {
  display: block;
  margin-top: 2px;
  color: var(--tud-color-text-secondary);
  font-size: 13px;
  font-weight: 700;
}
.nav-account-status[data-status="verified"] { color: var(--tud-color-status-success-text); }
.nav-account-status[data-status="pending"] { color: var(--tud-color-status-warning-text); }

.nav-account-item {
  display: flex;
  width: 100%;
  min-height: 44px;
  align-items: center;
  padding: 0 var(--tud-space-3);
  border: 0;
  border-radius: var(--tud-radius-sm);
  background: none;
  color: var(--tud-color-text-secondary);
  font-family: inherit;
  font-size: 15px;
  font-weight: 700;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}
.nav-account-item:hover { background: var(--tud-color-bg-sunken); color: var(--tud-color-text-primary); }

/* ---------- Site footer ---------- */

/* Signature footer markup lives in 28 places: public/index.html,
   public/deals/index.html, public/verify/index.html, public/profile/index.html,
   public/signin/index.html, public/launch/index.html, public/partners/index.html,
   public/partners/setup/index.html, public/partners/sdk/index.html,
   public/partners/shopify/index.html, public/partners/shopify/installed/index.html,
   public/partners/woocommerce/index.html, public/privacy/index.html,
   public/terms/index.html, public/support/index.html, public/cookies/index.html,
   public/cookies/settings/index.html, public/ad-disclosure/index.html,
   public/accessibility/index.html, public/confirm/index.html,
   public/check/index.html, public/delete-account/index.html, public/404.html,
   public/careers/index.html, public/careers/campus-ambassador/index.html,
   public/lucky-draw/index.html, functions/deals/[slug].js and
   functions/deals/index.js. Keep all 28 byte-consistent in structure.
   The social links are the exception: each footer carries only an empty
   Follow slot, and /assets/social.js fills it from its one list. */

.footer {
  position: relative;
  overflow: hidden;
  border-top: var(--tud-border-hairline) solid var(--tud-color-border-default);
  color: var(--tud-color-text-primary);
  background: var(--tud-color-bg-canvas);
}

.footer-utility {
  width: min(100% - (var(--tud-space-6) * 2), var(--site-content));
  margin-inline: auto;
  padding-top: clamp(var(--tud-space-12), 6vw, 88px);
  display: grid;
  grid-template-columns: minmax(220px, 1.4fr) minmax(0, 3fr);
  align-items: start;
  gap: clamp(var(--tud-space-8), 7vw, 104px);
}

.footer-tagline {
  max-width: 260px;
  font-family: var(--tud-font-secondary);
  font-size: clamp(21px, 2vw, 28px);
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.035em;
}

.footer-columns {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(var(--tud-space-6), 4vw, var(--tud-space-16));
}

.footer-column h2,
.footer-social h2 {
  margin: 0 0 var(--tud-space-3);
  color: var(--tud-color-text-muted);
  font-family: var(--tud-font-secondary);
  font-size: 12px;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.footer-links { display: grid; align-content: start; }

.footer-links a {
  min-width: 44px;
  min-height: 44px;
  display: flex;
  align-items: center;
  width: fit-content;
  color: var(--tud-color-text-secondary);
  font-size: 14px;
  font-weight: 650;
  line-height: 1.25;
  text-decoration: none;
}

.footer-links a:hover { color: var(--tud-color-text-primary); }

/* The company behind the brand. It lived in the Company column briefly and read
   as a broken first link: the column is a list of 44px destinations and a fact
   is neither. It belongs with the brand statement instead, where "who says
   this" is already the subject. The full registered name lives where it is
   legally load-bearing, on invoices and contracts, not here. */
.footer-company {
  margin: var(--tud-space-3) 0 0;
  color: var(--tud-color-text-muted);
  font-family: var(--tud-font-secondary);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* The name is also the way to the company's own site. It stays a fact at rest,
   which is why it takes the line's own colour and carries no underline until it
   is hovered or focused: an underlined link here would read as a fourth footer
   column with one entry. */
.footer-company a {
  position: relative;
  color: inherit;
  text-decoration: none;
}
/* The footer's links are 44px destinations. This one cannot grow to 44px
   without pushing the line off the brand statement it belongs to, so it grows
   its hit area instead and leaves the text exactly where it sits. */
.footer-company a::after {
  content: "";
  position: absolute;
  inset: -13.5px -8px;
}
.footer-company a:hover { text-decoration: underline; text-underline-offset: 3px; }
.footer-company a:focus-visible {
  outline: 3px solid var(--tud-color-border-focus);
  outline-offset: 2px;
  border-radius: var(--tud-radius-sm);
}

/* Follow sits under the brand statement, the one column with room to spare,
   and borrows the link columns' heading so it reads as a fourth list rather
   than a badge row. The glyphs are 20px inside 44px targets; the list is
   pulled out by the difference so the first glyph lines up with the tagline
   and the last one's spare hit area spills into the gutter, not the text. */
.footer-social { margin-top: 20px; }
/* The row sits right under Avenith, so the "Follow" heading stays for screen
   readers only; a visible label here pushed the icons into a block of their own. */
.footer-social h2 {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}
.footer-social-list {
  display: flex;
  flex-wrap: wrap;
  margin: 0 -12px;
  padding: 0;
  list-style: none;
}
.footer-social-link {
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  border-radius: var(--tud-radius-sm);
  color: var(--tud-color-text-secondary);
  transition: color var(--tud-duration-fast) var(--tud-ease-standard);
}
.footer-social-link svg { display: block; width: 20px; height: 20px; }
.footer-social-link:hover { color: var(--site-accent-text); }
.footer-social-link:focus-visible {
  outline: 3px solid var(--tud-color-border-focus);
  outline-offset: -3px;
  color: var(--tud-color-text-primary);
}
@media (prefers-reduced-motion: reduce) {
  .footer-social-link { transition: none; }
}

.footer-landmark {
  position: relative;
  margin-top: clamp(var(--tud-space-5), 2.5vw, 40px);
  overflow: hidden;
  isolation: isolate;
}

.footer-landmark-stage { position: relative; }

.footer-wordmark {
  position: relative;
  z-index: 1;
  /* Same width formula as .footer-utility, so the landmark and the link
     columns above it scale together as one composition. In normal flow the
     landmark is exactly as tall as the art, no reserved dead space. */
  width: min(100% - (var(--tud-space-6) * 2), var(--site-content));
  margin-inline: auto;
  aspect-ratio: 11504.2 / 2750;
}

.footer-landmark-image { display: block; width: 100%; height: auto; }




[data-theme="dark"] .footer-landmark-image {
  filter: invert(0.94) hue-rotate(180deg) saturate(1.15);
}

/* Compact footer: only the local launch page (public/launch/) carries the
   stacked-logo one-liner instead of the landmark. Every public page took the
   signature footer on 2026-08-05, so do not reintroduce this one on them. */
.footer-inner {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--tud-space-4) var(--tud-space-7);
  align-items: center;
  width: min(100% - (var(--tud-space-6) * 2), var(--site-content));
  margin-inline: auto;
  padding-block: var(--tud-space-8);
  border-top: var(--tud-border-hairline) solid var(--tud-color-border-default);
}
.footer-brand { grid-row: 1 / span 2; display: block; }
.footer-logo { display: block; width: 132px; height: auto; }
.footer-copy p { margin: 0; font-weight: 700; letter-spacing: -0.01em; }
.footer-copy .fine {
  margin-top: var(--tud-space-1);
  color: var(--tud-color-text-secondary);
  font-size: 13px;
  font-weight: 500;
}
.footer-nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--tud-space-2) var(--tud-space-5);
}
.footer-nav a {
  color: var(--tud-color-text-secondary);
  font-size: 14px;
  font-weight: 700;
  text-decoration: none;
}
.footer-nav a:hover { color: var(--tud-color-text-primary); text-decoration: underline; text-underline-offset: 3px; }
.footer-nav a:focus-visible { outline: 3px solid var(--tud-color-border-focus); outline-offset: 2px; border-radius: var(--tud-radius-sm); }

@media (max-width: 560px) {
  .footer-inner { grid-template-columns: 1fr; justify-items: start; }
  .footer-brand { grid-row: auto; }
  .footer-logo { width: 112px; }
}

@media (prefers-reduced-motion: no-preference) {
  .footer.is-footer-reveal-ready .footer-landmark-stage {
    opacity: 0;
    transform: translateY(24px);
  }

  .footer.is-footer-reveal-ready.is-footer-revealed .footer-landmark-stage {
    opacity: 1;
    transform: none;
    transition:
      opacity 440ms var(--tud-ease-decelerate),
      transform 440ms var(--tud-ease-decelerate);
  }
}

/* ---------- Landing hero ---------- */

.hero { padding-block: clamp(var(--tud-space-10), 7vw, 88px) clamp(var(--tud-space-16), 9vw, 112px); }
.hero-layout { display: grid; grid-template-columns: minmax(0, 1.08fr) minmax(340px, 0.92fr); align-items: center; gap: clamp(var(--tud-space-8), 6vw, 88px); }
.hero-copy { position: relative; z-index: 2; }
/* Brand orange, deliberately. The base orange reads at 2.4:1 on the light
   canvas, under even the 3:1 large-text floor, and the founder chose brand
   orange over that floor on 2026-08-04 so the headline accent matches the
   Get early access button exactly. This is a recorded product decision, not an
   oversight: do not silently swap it back to the darker text accent in a future
   accessibility pass. One rule covers both themes, because dark already
   resolved to this same #FF5C26. */
.hero-title-accent { position: relative; display: inline-block; color: var(--tud-color-accent-base); }

/* The promise gets underlined by hand. Drawn stroke, absolutely positioned so
   it reserves no space and shifts nothing, in ink rather than the word's own
   orange so it reads as a mark made on top of the line, not part of it. */
.hero-underline {
  position: absolute;
  right: 0;
  bottom: -0.1em;
  left: 0;
  z-index: -1;
  width: 100%;
  height: 0.2em;
  color: var(--tud-color-text-primary);
  overflow: visible;
  pointer-events: none;
}
.hero-copy .lede { margin-top: var(--tud-space-6); }

/* No overflow:hidden here. It clipped the select popover, and the accent
   stripe that originally needed clipping is gone. */
.join-card {
  position: relative;
  margin-top: var(--tud-space-8);
  scroll-margin-top: var(--tud-space-8);
  padding: var(--tud-space-6);
  border-radius: var(--tud-radius-xl);
  background: var(--tud-color-bg-surface);
  border: var(--tud-border-default) solid var(--tud-color-border-default);
  box-shadow: var(--tud-elevation-e3);
}

/* One card, three steps: email and campus, the 6-digit code, then the account.
   Only one .join-step is ever visible; /assets/join.js does the swapping. The
   spacing below is written against direct children on purpose, so the buttons
   and fine print inside the finished step's referral panel keep their own. */
.join-step-head { margin-bottom: var(--tud-space-5); }
/* Direct child only: the sub line names the address in a nested <strong>, and it
   has to stay inline at its own size inside that sentence. */
.join-step-head > strong { display: block; font-size: 18px; line-height: 1.25; }
.join-step-sub { margin-top: var(--tud-space-2); color: var(--tud-color-text-secondary); font-size: 14px; line-height: 1.5; }
.join-fields { display: grid; grid-template-columns: minmax(0, 1.18fr) minmax(0, 0.82fr); gap: var(--tud-space-3); }
.join-step > form > .btn { margin-top: var(--tud-space-4); }
.join-step > form > .fine { margin-top: var(--tud-space-3); }
.join-step > form > .form-msg { margin-top: var(--tud-space-2); }

/* The code arrives as one number, so it is set as one: wide tracking, tabular
   figures, centred. Mirrors .claim-otp-input in sdk/claim/claim.css, which is
   the same field in the hosted claim window. */
.code-input {
  font-family: var(--tud-font-secondary);
  font-size: 24px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.32em;
  text-align: center;
  text-indent: 0.32em; /* the trailing letter-space would otherwise pull it left */
}

/* Secondary doors out of a step: resend, change email, jump to the code. Never
   a button-looking thing, because none of them is the way forward. */
.join-step-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--tud-space-2) var(--tud-space-5);
  margin-top: var(--tud-space-3);
}

.link-button {
  display: inline-flex;
  min-height: 44px;
  align-items: center;
  padding: 0;
  border: 0;
  background: none;
  color: var(--site-accent-text);
  font-family: var(--tud-font-primary);
  font-size: 14px;
  font-weight: 800;
  text-decoration: underline;
  text-underline-offset: 0.18em;
  cursor: pointer;
}

.link-button:hover { text-decoration-thickness: 2px; }
.link-button[disabled] { color: var(--tud-color-text-muted); cursor: default; text-decoration: none; }

.success { padding: var(--tud-space-2) 0; }
.success .status-icon { margin-bottom: var(--tud-space-4); }
.success h2 { max-width: 18ch; font-size: 30px; line-height: 1.08; }
.success h2:focus-visible { outline: none; }
.success > p { max-width: 46ch; margin-block: var(--tud-space-3) var(--tud-space-5); color: var(--tud-color-text-secondary); }
.success > .fine { margin-top: var(--tud-space-3); }
.success > .btn + .btn { margin-top: var(--tud-space-3); }
.success > .fine + .btn { margin-top: var(--tud-space-4); }
.success > .referral-panel { margin-top: var(--tud-space-6); }

/* The setup ladder: account done, verify is the loud next rung, codes wait.
   Verification is the product's whole trust story, so it reads as the primary
   step on this panel, never as fine print. */
.join-progress {
  display: flex;
  align-items: center;
  gap: var(--tud-space-2);
  margin: 0 0 var(--tud-space-5);
  padding: 0;
  list-style: none;
}
.join-progress li {
  display: flex;
  align-items: center;
  gap: var(--tud-space-2);
  color: var(--tud-color-text-muted);
  font-family: var(--tud-font-secondary);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.join-progress li + li::before {
  content: "";
  display: block;
  width: 18px;
  height: 2px;
  margin-right: var(--tud-space-2);
  background: var(--tud-color-border-default);
}
.join-progress-dot {
  display: inline-flex;
  width: 24px;
  height: 24px;
  align-items: center;
  justify-content: center;
  border: 2px solid currentColor;
  border-radius: var(--tud-radius-pill);
  font-size: 12px;
}
.join-progress .is-done { color: var(--tud-color-status-success-text); }
.join-progress .is-next { color: var(--site-accent-text); }
@media (max-width: 420px) {
  .join-progress { gap: var(--tud-space-1); }
  .join-progress li + li::before { width: 10px; margin-right: var(--tud-space-1); }
  .join-progress-label { letter-spacing: 0.02em; }
}

/* The verify strip on deal pages: one persistent, unmissable line for a
   signed-in student who has not verified yet. Injected by app.js only when
   that is true, so the markup never ships to anyone else. */
.verify-nudge {
  border-bottom: var(--tud-border-hairline) solid var(--tud-color-accent-tint-border);
  background: var(--tud-color-accent-tint);
}
.verify-nudge-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--tud-space-4);
  width: min(100% - (var(--tud-space-6) * 2), var(--site-content));
  margin-inline: auto;
  padding-block: var(--tud-space-3);
}
.verify-nudge p { margin: 0; font-size: 15px; line-height: 1.45; }
.verify-nudge .btn { flex: none; }
@media (max-width: 640px) {
  .verify-nudge-inner { flex-direction: column; align-items: stretch; text-align: center; }
}
/* Inside a hero card the big confirm-page counter would shout over the invite
   link it belongs to, so it steps down a size, tightens up, and shares its row
   with the share button instead of taking one of its own. */
.referral-share {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--tud-space-3);
  margin-top: var(--tud-space-4);
}
/* No box around the count here: on the tinted panel the only thing that should
   look pressable is the share button next to it. */
.success .referral-stat {
  margin: 0;
  padding: 0;
  justify-content: flex-start;
  gap: var(--tud-space-3);
  border: 0;
  background: none;
}
.success .referral-stat-value { font-size: 30px; }
.success .referral-panel .fine { margin-top: var(--tud-space-2); }
.referral-fallback-text { font-weight: 700; line-height: 1.45; }
#join-referral-fallback .btn { margin-top: var(--tud-space-4); }

.hero-visual { position: relative; min-height: 560px; display: grid; place-items: center; }

/* A single unboxed student-life moment replaces the second card. */
.hero-doodle-stage {
  position: relative;
  isolation: isolate;
  width: min(100%, 540px);
  aspect-ratio: 1;
  display: grid;
  place-items: center;
}

.hero-doodle { position: relative; z-index: 1; display: block; width: 112%; transform: rotate(-1deg); }
.hero-doodle img { width: 100%; }

/* ---------- Landing sections ---------- */

.steps-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--tud-space-5); }
.step-card { position: relative; min-height: 100%; padding: var(--tud-space-6); }
.step-number {
  position: absolute;
  top: var(--tud-space-5);
  right: var(--tud-space-5);
  color: var(--site-accent-text);
  font-family: var(--tud-font-secondary);
  font-size: 14px;
  font-weight: 700;
}
.step-card .theme-art { display: grid; min-height: 152px; place-items: center; margin-bottom: var(--tud-space-4); }
.step-card img { width: auto; height: 138px; object-fit: contain; }
.step-card h3 { margin-bottom: var(--tud-space-2); font-size: 21px; line-height: 1.2; }
.step-card p { color: var(--tud-color-text-secondary); }

.cats-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: var(--tud-space-4); }
.cat-card {
  min-height: 190px;
  padding: var(--tud-space-5) var(--tud-space-3);
  display: grid;
  grid-template-rows: 1fr auto;
  place-items: center;
  gap: var(--tud-space-3);
  border: var(--tud-border-default) solid var(--tud-color-accent-tint-border);
  border-radius: var(--tud-radius-lg);
  background: var(--tud-color-bg-sunken);
  color: inherit;
  text-align: center;
  text-decoration: none;
}
/* The tile lifts and a drawn shadow-line appears under it: the same offset
   block shadow the connect slip already uses, which is this system's answer to
   a hover glow. Zero blur, because the world is drawn, not lit. */
.cat-card {
  box-shadow: 0 0 0 var(--tud-color-accent-base);
  transition:
    transform var(--tud-duration-base) var(--tud-ease-standard),
    border-color var(--tud-duration-base) var(--tud-ease-standard),
    box-shadow var(--tud-duration-base) var(--tud-ease-standard);
}
.cat-card:hover,
.cat-card:focus-visible {
  border-color: var(--tud-color-accent-base);
  box-shadow: 4px 5px 0 var(--tud-color-accent-base);
  transform: translateY(-2px);
}
.cat-card:active { transform: translateY(0); box-shadow: 1px 1px 0 var(--tud-color-accent-base); }
.cat-card .theme-art { display: grid; place-items: center; }
.cat-card img { width: auto; height: 104px; object-fit: contain; }
.cat-card span { font-weight: 800; }

.merchant-card {
  position: relative;
  padding: clamp(var(--tud-space-8), 6vw, var(--tud-space-16));
  overflow: hidden;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: end;
  gap: var(--tud-space-10);
  border-radius: var(--tud-radius-xl);
  color: var(--tud-color-text-inverse);
  background: var(--tud-color-bg-inverse);
}
.merchant-card::after {
  position: absolute;
  top: -72px;
  right: 14%;
  width: 160px;
  height: 160px;
  border: 30px solid var(--tud-color-accent-base);
  border-radius: var(--tud-radius-pill);
  content: "";
  opacity: 0.34;
}
.merchant-card-copy { position: relative; z-index: 1; }
.merchant-card .overline { color: var(--tud-color-accent-base); }
.merchant-card h2 { max-width: 16ch; font-size: clamp(34px, 5vw, 52px); line-height: 1.02; }
.merchant-card p { max-width: 61ch; margin-top: var(--tud-space-4); color: var(--tud-color-ramp-neutral-300); }
.merchant-card .btn { position: relative; z-index: 1; }

.faq-layout { display: grid; grid-template-columns: minmax(220px, 0.6fr) minmax(0, 1.4fr); gap: clamp(var(--tud-space-8), 7vw, 88px); }
.faq-intro { align-self: start; position: sticky; top: 112px; }
.faq-intro p { max-width: 30ch; margin-top: var(--tud-space-4); color: var(--tud-color-text-secondary); }
.faq-list { border-top: var(--tud-border-default) solid var(--tud-color-border-default); }
.faq-list details { border-bottom: var(--tud-border-default) solid var(--tud-color-border-default); }
.faq-list summary {
  min-height: 64px;
  padding: var(--tud-space-5) 56px var(--tud-space-5) 0;
  display: flex;
  align-items: center;
  position: relative;
  font-size: 17px;
  font-weight: 800;
  line-height: 1.35;
  letter-spacing: -0.012em;
  cursor: pointer;
  list-style: none;
  transition: color var(--tud-duration-fast) var(--tud-ease-standard);
}
.faq-list summary::-webkit-details-marker { display: none; }
.faq-list summary:hover { color: var(--site-accent-text); }
.faq-list summary:focus-visible { outline-offset: -1px; }
/* Plus that becomes a minus: two bars drawn as backgrounds, the upright one
   rotating flat onto the other. A rotated plus would read as a close cross,
   and swapping "+" for "-" cannot be animated at all. */
.faq-list summary::before,
.faq-list summary::after {
  position: absolute;
  top: 50%;
  right: var(--tud-space-3);
  width: 26px;
  height: 26px;
  content: "";
  color: var(--site-accent-text);
  background-image: linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  background-position: center;
  transform: translateY(-50%);
}
.faq-list summary::after {
  border: var(--tud-border-hairline) solid var(--tud-color-border-strong);
  border-radius: var(--tud-radius-pill);
  background-size: 11px 2px;
  transition:
    border-color var(--tud-duration-fast) var(--tud-ease-standard),
    background-color var(--tud-duration-fast) var(--tud-ease-standard);
}
.faq-list summary::before {
  z-index: 1;
  background-size: 2px 11px;
  transition: transform var(--tud-duration-base) var(--tud-ease-standard);
}
.faq-list summary:hover::after { border-color: var(--tud-color-accent-tint-border); background-color: var(--tud-color-accent-tint); }
.faq-list details[open] summary::before { transform: translateY(-50%) rotate(90deg); }
.faq-list details[open] summary { color: var(--tud-color-text-primary); }
.faq-list details p {
  max-width: 62ch;
  padding: 0 56px var(--tud-space-6) 0;
  color: var(--tud-color-text-secondary);
  font-size: 15px;
  line-height: 1.65;
}

/* ---------- Confirmation page ---------- */

.confirm-main { padding-inline: var(--tud-space-6); }
.confirm-card { width: min(100%, 680px); padding: clamp(var(--tud-space-6), 5vw, var(--tud-space-10)); text-align: center; }
.confirm-card h1 { font-size: clamp(32px, 5vw, 44px); line-height: 1.06; }
.confirm-card h1 + p { max-width: 48ch; margin: var(--tud-space-3) auto var(--tud-space-6); color: var(--tud-color-text-secondary); }
.confirm-loading-text { color: var(--tud-color-text-muted); }
/* The art wrapper is inline by default, so width and margin:auto are ignored
   on it and the child img resolves width:100% against the card. Must be block. */
.reward-art { display: block; width: 200px; margin: 0 auto var(--tud-space-3); }
.reward-art img { width: 100%; }

/* ---------- Referral hub ----------
   The invite link, its counter and the WhatsApp share. Two pages render it: the
   emailed confirmation page (public/confirm/) and the finished step of the
   homepage signup card. Class-based on purpose, so both can carry it. */

.referral-panel {
  padding: var(--tud-space-5);
  border: var(--tud-border-default) solid var(--tud-color-accent-tint-border);
  border-radius: var(--tud-radius-lg);
  background: var(--tud-color-bg-sunken);
  text-align: left;
}
.referral-link-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--tud-space-2); margin-top: var(--tud-space-2); }
.referral-link-input { font-family: var(--tud-font-secondary); font-size: 14px; }
.referral-copy-status { min-height: 19px; margin-top: var(--tud-space-2); }
.referral-stat {
  margin-block: var(--tud-space-5);
  padding: var(--tud-space-4) var(--tud-space-5);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--tud-space-5);
  border: var(--tud-border-default) solid var(--tud-color-border-default);
  border-radius: var(--tud-radius-lg);
  background: var(--tud-color-bg-surface);
  text-align: left;
}
.referral-stat-label { color: var(--tud-color-text-secondary); font-weight: 700; }
.referral-stat-value { color: var(--site-accent-text); font-family: var(--tud-font-secondary); font-size: 44px; font-weight: 700; line-height: 1; }
.confirm-actions { display: grid; }
.confirm-refer { margin-top: var(--tud-space-4); }

/* ---------- Partner page ----------
   Three acts stacked in one column: the claim, the working product that backs
   it, then the ask. There is deliberately no sticky column and no second track.
   The pitch used to sit in a 0.8fr column beside the form with the sticky moved
   onto an inner wrapper, because a sticky grid item is held by the grid's
   content box and slid over the section below it. With the form moved under the
   proof there is nothing left for a sticky column to accompany, so the whole
   mechanism is gone rather than nursed. */

.partners-main {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  row-gap: clamp(var(--tud-space-12), 7vw, 96px);
}

/* Act one. The pitch and the ask share one hero now, in the same 1.08fr/0.92fr
   split the homepage hero uses for its own claim-beside-a-card pairing, so the
   two hero treatments across the site read as one language. Stretched, not
   centred: the copy column takes the full row height, which is what lets its
   own last child anchor to the row's true bottom below. */
.partners-pitch {
  display: grid;
  grid-template-columns: minmax(0, 1.08fr) minmax(360px, 0.92fr);
  align-items: stretch;
  gap: clamp(var(--tud-space-8), 6vw, 72px);
}
/* A flex column so the drawing can be pinned to its foot: headline, lede and
   bullets keep their natural height, and the last child's auto top margin
   claims whatever the row leaves over, which is the form card's own height
   minus the text above. That is what makes the drawing's foot land exactly on
   the form's foot without measuring either one in script. */
.partners-pitch-copy {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.partners-pitch .page-title { max-width: 16ch; }
.partners-pitch .lede { margin-top: var(--tud-space-5); }
/* The drawing used to fill this whole column on its own; now it closes the
   column instead, grown to use the space the shorter copy leaves above it.
   The height is the size knob (auto width follows the art's own proportions);
   the cap keeps it from turning into a poster on a very tall, sparsely-filled
   row. margin-top: auto does the actual pinning, and only bites once the flex
   column has a real height to spend it against, which .partners-pitch's
   stretch is what supplies. */
.partners-art {
  display: block;
  margin-top: auto;
  padding-top: clamp(var(--tud-space-8), 5vw, var(--tud-space-10));
}
.partners-art img { display: block; width: auto; max-width: 100%; height: clamp(240px, 30vw, 420px); }
.partners-benefits { display: grid; gap: var(--tud-space-4); margin: var(--tud-space-8) 0 0; padding: 0; list-style: none; }
.partners-benefits li { display: flex; gap: var(--tud-space-3); max-width: 62ch; color: var(--tud-color-text-secondary); }
.partners-benefits li::before {
  flex: 0 0 auto;
  width: 9px;
  height: 9px;
  margin-top: 0.52em;
  border-radius: var(--tud-radius-pill);
  content: "";
  background: var(--tud-color-accent-base);
}
/* The one claim on the page a neighbouring product could not truthfully make,
   set a step up in size, weight and colour so it stops reading as the first of
   three equal bullets. */
.partners-benefit-lead {
  color: var(--tud-color-text-primary);
  font-size: clamp(17px, 1.5vw, 19px);
  font-weight: 700;
  line-height: 1.5;
}
.partners-benefit-lead::before { width: 11px; height: 11px; margin-top: 0.5em; }

/* ---------- Partner page: the proof ---------- */

.partners-portal {
  /* A grid track's automatic minimum is min-content, and the phone rule below
     pins the poster to a fixed width inside its own scroller. Without this the
     image would size the whole page's column and push it past the viewport. */
  min-width: 0;
  padding-top: clamp(var(--tud-space-10), 5vw, 72px);
  border-top: var(--tud-border-default) solid var(--tud-color-border-default);
}
.portal-heading h2 { max-width: 20ch; }

/* A screenshot is evidence, so it is filed rather than floated: a bordered
   plate with a Space Grotesk label strip across the top naming the screen it
   came from. No browser chrome, no perspective, no drop shadow. */
.portal-plate {
  margin: 0;
  overflow: hidden;
  border: var(--tud-border-strong) solid var(--tud-color-accent-tint-border);
  border-radius: var(--tud-radius-lg);
  background: var(--tud-color-bg-surface);
}
/* The tinted strip is the same border-plus-tint pairing the category tiles and
   the recommended setup card use, so the plate reads as an object of this site
   in both themes rather than a screenshot dropped on the canvas. */
.portal-plate-cap {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--tud-space-2) var(--tud-space-3);
  padding: var(--tud-space-3) var(--tud-space-4);
  border-bottom: var(--tud-border-hairline) solid var(--tud-color-accent-tint-border);
  color: var(--tud-color-text-muted);
  background: var(--tud-color-accent-tint);
  font-family: var(--tud-font-secondary);
  font-size: 12px;
  font-weight: 700;
  line-height: 16px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.portal-plate-tab { color: var(--site-accent-text); }
.portal-plate-art { display: block; }
.portal-plate-art img { width: 100%; }

/* The live portal, filed in the same plate as the screenshot it replaces, so
   the interactive thing is presented as evidence rather than as a widget. The
   captured screenshot stays underneath as the poster: it is what shows before
   the frame has loaded, what shows on a phone, and what shows with JavaScript
   off, which is why the light and dark pair is still in the markup. */
.portal-demo {
  margin: 0;
  overflow: hidden;
  border: var(--tud-border-strong) solid var(--tud-color-accent-tint-border);
  border-radius: var(--tud-radius-lg);
  background: var(--tud-color-bg-surface);
}
.portal-demo-viewport { position: relative; }
/* The height here is only the fallback: partners.js replaces it with the
   frame's own reported content height, because the portal's mobile layout is
   much taller than its desktop one and one number cannot serve both. Growing
   the frame to fit beats an inner scrollbar, which on a phone fights the page
   scroll for the same gesture. */
.portal-demo-stage {
  position: relative;
  background: var(--tud-color-bg-canvas);
  transition: height var(--tud-duration-slow) var(--tud-ease-standard);
}
/* Without JavaScript there is no frame, so the plate is simply the captured
   screenshot at its own proportions. The fixed height and the poster's move out
   of the flow only arrive once partners.js has taken the stage over, which
   keeps the no-script page honest instead of cropping a screenshot to a box
   that exists for a frame that is never coming. */
.portal-demo-poster { display: block; }
.portal-demo-poster img { width: 100%; height: auto; }
.portal-demo-stage.is-demo-framed { height: clamp(460px, 62vh, 660px); }
.portal-demo-stage.is-demo-framed .portal-demo-poster { position: absolute; inset: 0; }
.portal-demo-stage.is-demo-framed .portal-demo-poster img {
  height: 100%;
  object-fit: cover;
  object-position: top center;
}
.portal-demo-frame {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--tud-duration-slow) var(--tud-ease-decelerate);
}
/* Set by partners.js on the iframe's own load event, so the poster is never
   pulled away before there is something behind it. */
.portal-demo-stage.is-demo-live .portal-demo-frame { opacity: 1; pointer-events: auto; }
.portal-demo-stage.is-demo-live .portal-demo-poster { opacity: 0; }

.portal-demo-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--tud-space-3) var(--tud-space-5);
  padding: var(--tud-space-4);
  border-top: var(--tud-border-hairline) solid var(--tud-color-accent-tint-border);
  background: var(--tud-color-accent-tint);
}
.portal-demo-foot .fine { max-width: 62ch; }
.portal-demo-open { flex: 0 0 auto; }
.portal-demo-open svg { flex: none; }

.portal-detail {
  margin-top: clamp(var(--tud-space-8), 5vw, var(--tud-space-16));
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(280px, 340px);
  align-items: start;
  gap: clamp(var(--tud-space-8), 5vw, 64px);
}

/* The same ruled dt/dd spec sheet as .enforce-ledger on the setup guide: four
   parallel capabilities, so rows under hairlines rather than numbered steps. */
.portal-ledger { margin: 0; border-top: var(--tud-border-hairline) solid var(--tud-color-border-default); }
.portal-row {
  display: grid;
  grid-template-columns: minmax(180px, 260px) 1fr;
  gap: var(--tud-space-2) var(--tud-space-8);
  align-items: baseline;
  padding-block: var(--tud-space-5);
  border-bottom: var(--tud-border-hairline) solid var(--tud-color-border-default);
}
.portal-row dt { font-weight: 700; color: var(--tud-color-text-primary); }
.portal-row dd { max-width: 58ch; margin: 0; color: var(--tud-color-text-secondary); }

/* ---------- Partner page: the ask ----------
   The ask now lives in the hero's own second column, so the card fills its
   grid track rather than centring itself on the full canvas. The 560px cap
   only bites once the pitch has stacked to one column below, where the track
   is the whole container and would otherwise stretch the fields too wide. */

.partners-form-card {
  min-width: 0;
  width: 100%;
  max-width: 560px;
  margin-inline: auto;
  padding: clamp(var(--tud-space-6), 5vw, var(--tud-space-8));
  scroll-margin-top: 88px;
}
.partners-form-heading { margin-bottom: var(--tud-space-6); }
.partners-form-heading h2 { margin-bottom: var(--tud-space-2); font-size: 28px; line-height: 1.12; }
/* :not(.fine) so the setup-guide line keeps the muted weight its own class
   sets. The bare descendant selector used to beat .fine on specificity. */
.partners-form-heading p:not(.fine) { color: var(--tud-color-text-secondary); }
.partners-form-heading .fine { margin-top: var(--tud-space-3); }

/* Four channels, one of which is enough, so the note that says so sits with
   them rather than under a single field. */
.form-group-note { max-width: 62ch; margin-bottom: calc(var(--tud-space-1) * -1); }
.field-help { margin-top: calc(var(--tud-space-1) * -1); max-width: 62ch; }

/* The button and the sentence that makes it safe to press are one object. */
.form-commit { display: grid; gap: var(--tud-space-3); justify-items: start; }
.form-reassure { max-width: 54ch; }

.partners-success { padding-block: var(--tud-space-12); text-align: center; }
.partners-success:focus { outline: none; }
.partners-success .status-icon { margin: 0 auto var(--tud-space-4); }
/* The reward heading, not a smaller echo of the question that earned it. */
.partners-success h2 { margin-bottom: var(--tud-space-3); font-size: 34px; line-height: 1.08; }
.partners-success p { max-width: 46ch; margin-inline: auto; color: var(--tud-color-text-secondary); }
.partners-success .fine { margin-top: var(--tud-space-4); }
.partners-success-email { font-weight: 800; color: var(--tud-color-text-primary); overflow-wrap: anywhere; }

/* Its own row under the card, explicitly in the card's column: without the
   line, auto-placement would drop it back under the pitch copy instead. Kept
   out of the card's own box on purpose, so the row above (copy stretched
   against card) never has to answer for its height too, which is what keeps
   the drawing's foot lined up with the card's foot rather than this line's. */
.partners-portal-signin {
  grid-column: 2;
  max-width: 560px;
  margin: var(--tud-space-6) auto 0;
}

/* ---------- Legal pages ---------- */

.legal { width: min(100% - (var(--tud-space-6) * 2), var(--site-reading)); margin-inline: auto; }
.legal-header {
  margin-bottom: var(--tud-space-10);
  padding-bottom: var(--tud-space-8);
  border-bottom: var(--tud-border-default) solid var(--tud-color-border-default);
}
.legal-effective { margin-top: var(--tud-space-3); color: var(--tud-color-text-muted); font-family: var(--tud-font-secondary); font-size: 13px; line-height: 1.5; }
.legal-intro { margin-top: var(--tud-space-6); color: var(--tud-color-text-secondary); font-size: 18px; line-height: 1.65; }
.legal section { margin-bottom: var(--tud-space-8); scroll-margin-top: 100px; }
.legal h2 { margin-bottom: var(--tud-space-3); font-size: 22px; line-height: 1.25; letter-spacing: -0.02em; }
.legal p, .legal ul { color: var(--tud-color-text-secondary); line-height: 1.7; }
.legal p { margin-bottom: var(--tud-space-3); }
.legal ul { margin: 0 0 var(--tud-space-3); padding-left: 1.25em; }
.legal li { margin-bottom: var(--tud-space-2); padding-left: var(--tud-space-1); }
.legal a { font-weight: 800; }
.legal-cross {
  margin-top: var(--tud-space-12);
  padding-top: var(--tud-space-6);
  border-top: var(--tud-border-default) solid var(--tud-color-border-default);
  font-weight: 800;
}
.legal-cross a { min-height: 44px; display: inline-flex; align-items: center; }

/* A legal page is the one place where the reading measure is the whole design,
   so the drawing is set as marginalia rather than handed a column of its own:
   it floats into the passage it annotates and the prose closes back around it.
   Nothing above or below that passage moves, which is the point. */
.legal-art {
  float: right;
  width: clamp(104px, 17vw, 148px);
  margin: var(--tud-space-1) 0 var(--tud-space-4) var(--tud-space-6);
}
.legal-art img { display: block; width: 100%; height: auto; }

/* Seated against the intro rather than the title, so the drawing annotates the
   promise the sentence makes instead of decorating the masthead. The intro runs
   shorter than the drawing is tall, so it pulls up into the date line above and
   lets the paragraph settle against its middle. */
.legal-art--intro { margin-top: calc(var(--tud-space-8) * -1); }

/* Float containment. The header already carries a bottom border, so it never
   collapsed a margin through it and flow-root costs it nothing. A section
   would have changed its bottom gap, so its last child hands that margin back. */
.legal-header { display: flow-root; }
.legal-section--art { display: flow-root; }
.legal-section--art > :last-child { margin-bottom: 0; }

/* The cookie pages name real storage keys, and a key is a literal string the
   reader may have to match character for character in their own browser. The
   site has no monospace face, so it borrows the platform's, the same way
   .code-inline does on the widget docs. */
.legal code {
  padding: 2px 6px;
  border: var(--tud-border-hairline) solid var(--tud-color-accent-tint-border);
  border-radius: var(--tud-radius-xs);
  color: var(--site-accent-text);
  background: var(--tud-color-accent-tint);
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
  font-size: 0.86em;
  white-space: nowrap;
}

/* An FAQ inside the reading column. .faq-list owns the answer's colour and its
   padding; only the paragraph margin .legal sets for prose has to be handed
   back, or every answer would carry a stray gap under it. */
.legal .faq-list { margin-bottom: var(--tud-space-3); }
.legal .faq-list p { margin-bottom: 0; }

/* ---------- Cookie policy: the storage table ---------- */

/* Same ruled surface as .ref-table on the widget docs, with one difference that
   matters here: this table sits in a 720px reading column and its rows are
   sentences, not identifiers, so under 640px it reflows into labelled blocks
   rather than scrolling sideways. A policy nobody can read on a phone is not a
   policy. The wrapper still scrolls, for the widths in between. */
.storage-table-wrap {
  margin-block: clamp(var(--tud-space-6), 3vw, var(--tud-space-8));
  overflow-x: auto;
  border: var(--tud-border-default) solid var(--tud-color-border-default);
  border-radius: var(--tud-radius-lg);
  background: var(--tud-color-bg-surface);
}

.storage-table { width: 100%; min-width: 520px; border-collapse: collapse; text-align: left; }

.storage-table caption {
  padding: var(--tud-space-4) var(--tud-space-5);
  border-bottom: var(--tud-border-hairline) solid var(--tud-color-border-default);
  color: var(--tud-color-text-muted);
  font-size: 13px;
  line-height: 1.5;
  text-align: left;
}

.storage-table th,
.storage-table td {
  padding: var(--tud-space-4) var(--tud-space-5);
  border-bottom: var(--tud-border-hairline) solid var(--tud-color-border-default);
  vertical-align: top;
}

.storage-table thead th {
  color: var(--tud-color-text-muted);
  background: var(--tud-color-bg-sunken);
  font-family: var(--tud-font-secondary);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.storage-table tbody th { width: 190px; font-weight: 700; }
.storage-table tbody td { color: var(--tud-color-text-secondary); font-size: 15px; line-height: 1.6; }
.storage-table tbody tr:last-child th,
.storage-table tbody tr:last-child td { border-bottom: 0; }

/* localStorage or sessionStorage: the mechanism, which belongs with the lifetime
   it explains rather than in a fourth column that would say one of two words. */
.storage-where {
  display: block;
  margin-top: var(--tud-space-1);
  color: var(--tud-color-text-muted);
  font-family: var(--tud-font-secondary);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* The table's own breakpoint, not the page's, which is why it sits here rather
   than with the consolidated ones at the foot of this file: three columns of
   sentences give up at about 640px whatever else the layout is doing. Each row
   becomes a block and each cell announces its own column through data-label.
   Switching a table to display:block is what drops its role in most engines,
   which is why the markup carries explicit table/row/cell roles: they cost
   nothing while the table is a table and keep it a table once it is not. The
   header row is clipped rather than deleted, so it stays in the accessibility
   tree for anyone navigating by column. */
@media (max-width: 640px) {
  .storage-table-wrap { overflow-x: visible; }
  .storage-table { min-width: 0; }
  .storage-table thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; }
  .storage-table caption { padding-inline: var(--tud-space-4); }
  .storage-table tbody tr {
    display: block;
    padding: var(--tud-space-5) var(--tud-space-4);
    border-bottom: var(--tud-border-hairline) solid var(--tud-color-border-default);
  }
  .storage-table tbody tr:last-child { border-bottom: 0; }
  .storage-table tbody th,
  .storage-table tbody td { display: block; width: auto; padding: 0; border-bottom: 0; }
  .storage-table tbody td { margin-top: var(--tud-space-3); }
  .storage-table tbody td::before {
    display: block;
    margin-bottom: var(--tud-space-1);
    content: attr(data-label);
    color: var(--tud-color-text-muted);
    font-family: var(--tud-font-secondary);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
  }
}

/* ---------- Cookie settings: the live ledger ---------- */

/* Scoped to .legal because that is the shell every one of these pages uses, and
   its own list rules (an indent, a bullet gap, a bottom margin) are written for
   prose lists and would otherwise push this ledger off the column's left edge. */
.legal .storage-live {
  margin: clamp(var(--tud-space-6), 3vw, var(--tud-space-8)) 0 0;
  padding: 0;
  border-top: var(--tud-border-hairline) solid var(--tud-color-border-default);
  list-style: none;
}

/* The state column is a fixed measure rather than a content-sized track, because
   every row is its own grid: sized to content, each row set its own column and
   the six answers stepped diagonally down the page instead of reading as one
   list. 152px holds every state this page writes on one line ("Saved as
   "FRIEND24"" is the longest at ~141px); the one state that can be longer is a
   value the visitor arrived with, and that one wraps inside the column rather
   than moving it. */
.legal .storage-live-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 152px auto;
  align-items: start;
  gap: var(--tud-space-3) var(--tud-space-6);
  margin-bottom: 0;
  padding: var(--tud-space-5) 0;
  border-bottom: var(--tud-border-hairline) solid var(--tud-color-border-default);
}

.storage-live-name { margin-bottom: 0; font-weight: 700; color: var(--tud-color-text-primary); }
.storage-live-desc {
  max-width: 52ch;
  margin: var(--tud-space-1) 0 0;
  color: var(--tud-color-text-secondary);
  font-size: 14px;
  line-height: 1.55;
}

/* The answer the visitor came for, so it is set in the numeric face the rest of
   the site uses for read-outs, and it says its own state rather than relying on
   a colour: "Not set" is grey because it is quiet, not because grey means off. */
.storage-live-state {
  margin-bottom: 0;
  padding-top: 2px;
  color: var(--tud-color-text-primary);
  font-family: var(--tud-font-secondary);
  font-size: 14px;
  font-weight: 700;
  /* A stored campus slug or referral code is one unbroken token as often as not,
     so it is allowed to break mid-word: the alternative is a value that runs
     out past the button and off the row. */
  overflow-wrap: anywhere;
}
.storage-live-state[data-tud-stored="no"] { color: var(--tud-color-text-muted); font-weight: 500; }
.storage-live-state[data-tud-stored="blocked"] { color: var(--tud-color-status-warning-text); }

/* Sign out is a wider word than Clear, and left to size itself it dragged its
   own row's button out of line with the other five. A shared floor width holds
   every button on one column, on both edges. */
.storage-live-clear { justify-self: end; min-width: 96px; white-space: nowrap; }
/* Disabled here means "nothing to remove", which is a resting state and not a
   wait, so it must not borrow the busy cursor .btn:disabled uses for saving. */
.storage-live-clear:disabled,
.storage-clear-all:disabled { cursor: default; }

.storage-actions { margin-top: var(--tud-space-6); }
/* .legal styles prose paragraphs, and these three are furniture rather than
   prose, so each one takes its colour back explicitly. */
.legal .storage-actions-note { max-width: 62ch; margin: var(--tud-space-3) 0 0; color: var(--tud-color-text-muted); }

.legal .storage-status { margin-bottom: 0; font-size: 14px; font-weight: 700; }
.storage-status:not(:empty) {
  margin-top: var(--tud-space-5);
  padding: var(--tud-space-3) var(--tud-space-4);
  border-radius: var(--tud-radius-lg);
  color: var(--tud-color-status-success-text);
  background: var(--tud-color-status-success-bg);
}
.storage-status[data-tone="warn"]:not(:empty) {
  color: var(--tud-color-status-warning-text);
  background: var(--tud-color-status-warning-bg);
}

/* No JavaScript, so no list to read. Same chip as the status line, in the
   informational key, because this is a fact about the page and not a failure. */
.legal .storage-note {
  margin-block: var(--tud-space-5);
  padding: var(--tud-space-4) var(--tud-space-5);
  border-radius: var(--tud-radius-lg);
  color: var(--tud-color-status-info-text);
  background: var(--tud-color-status-info-bg);
  font-size: 14px;
}
.legal .storage-note a { color: inherit; }

/* ---------- Merchant setup guide ---------- */

.setup-hero .lede { margin-top: var(--tud-space-5); }
.setup-hero .fine { margin-top: var(--tud-space-3); }

.option-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); align-items: start; gap: var(--tud-space-5); }
.option-card { padding: var(--tud-space-6); }
.option-card--recommended { border-color: var(--tud-color-accent-tint-border); box-shadow: var(--tud-elevation-e2); }
.option-badge { margin-bottom: var(--tud-space-4); }
.option-card h3 { margin-bottom: var(--tud-space-2); font-size: 21px; line-height: 1.2; }
.option-card > p { color: var(--tud-color-text-secondary); }

/* A condensed variant of .faq-list for per-platform steps nested inside an
   option card: same disclosure mechanics, smaller type and chevron so it
   reads as detail, not a second-level FAQ. */
.option-detail-list { margin-top: var(--tud-space-5); }
.option-detail-list summary { min-height: 48px; padding: var(--tud-space-3) 32px var(--tud-space-3) 0; font-size: 14px; }
.option-detail-list summary::before, .option-detail-list summary::after { right: 0; width: 20px; height: 20px; }
.option-detail-list summary::after { background-size: 9px 2px; }
.option-detail-list summary::before { background-size: 2px 9px; }
.option-detail-list details p { padding: 0 0 var(--tud-space-4); font-size: 14px; }
.option-detail-list details:last-child { border-bottom: none; }

/* Enforcement reads as a spec sheet, not bullet points: guarantee | mechanism
   rows under hairline rules, the same ledger language the brand pages use for
   price rows. Parallel guarantees, so no numbering. */
.enforce-ledger { margin: var(--tud-space-2) 0 0; border-top: var(--tud-border-hairline) solid var(--tud-color-border-default); }
.enforce-row {
  display: grid;
  grid-template-columns: minmax(220px, 300px) 1fr;
  gap: var(--tud-space-2) var(--tud-space-8);
  align-items: baseline;
  padding-block: var(--tud-space-5);
  border-bottom: var(--tud-border-hairline) solid var(--tud-color-border-default);
}
.enforce-row dt { font-weight: 700; color: var(--tud-color-text-primary); }
.enforce-row dd { margin: 0; color: var(--tud-color-text-secondary); max-width: 62ch; }

/* Five rules of policy is the passage a merchant reads to decide whether to
   trust us, and it is the one stretch of this page with nothing to look at.
   The student it is all written about sits beside it in a fixed column, so the
   rows keep the measure they were set to and the drawing never bids for their
   width. */
.enforce-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 260px;
  align-items: center;
  gap: clamp(var(--tud-space-8), 6vw, 72px);
}
.enforce-art { display: block; width: 100%; }
.enforce-art img { display: block; width: 100%; height: auto; }

/* ---------- Shopify installed page ---------- */

/* A success state gets the brand's slip, not more prose: the merchant-side
   twin of the claim slip students see, with the same ruled rows and a
   perforation before the fine print. */
.installed-hero {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(280px, 400px);
  gap: var(--tud-space-10);
  align-items: center;
}
/* The slip states the terms of the connection; this signs it. It sits under the
   lede in the copy column, so the celebration reads as part of the sentence
   that announced it rather than as an ornament parked in the margin. */
/* The one warm moment on a page that is otherwise a receipt, so it is sized to
   be looked at rather than noticed. At the old 112px ceiling it read as a
   stray mark beside a 60px headline. */
.installed-nod { display: block; width: clamp(148px, 17vw, 208px); margin-top: var(--tud-space-8); }
.installed-nod img { display: block; width: 100%; height: auto; }

.connect-slip {
  padding: var(--tud-space-6);
  background: var(--tud-color-bg-surface);
  border: var(--tud-border-strong) solid var(--tud-color-text-primary);
  border-radius: var(--tud-radius-lg);
  box-shadow: var(--tud-space-2) var(--tud-space-2) 0 var(--tud-color-accent-base);
}
.connect-slip-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--tud-color-accent-base);
  color: var(--tud-color-text-on-accent);
}
.connect-slip-title {
  margin: var(--tud-space-4) 0 var(--tud-space-2);
  font-weight: 700;
  color: var(--tud-color-text-primary);
}
/* Drawn, never typed: U+2194 renders as the blue emoji on Apple devices.
   display is restated because the global `img, svg { display: block }` reset
   applies here too, and a block arrow between two inline phrases broke the
   line into three: store, arrow, platform. The arrow reads left-to-right, so
   it only makes sense on one line with a name at each end. */
.connect-slip-arrow { display: inline-block; vertical-align: -1px; margin-inline: 4px; }
.connect-slip-rows { margin: 0; }
.connect-slip-rows > div {
  display: flex;
  justify-content: space-between;
  gap: var(--tud-space-4);
  padding-block: var(--tud-space-2);
  border-top: var(--tud-border-hairline) solid var(--tud-color-border-default);
}
.connect-slip-rows dt { color: var(--tud-color-text-secondary); }
.connect-slip-rows dd { margin: 0; font-weight: 700; color: var(--tud-color-text-primary); text-align: end; }
.connect-slip-foot {
  margin: var(--tud-space-4) 0 0;
  padding-top: var(--tud-space-3);
  border-top: var(--tud-border-hairline) dashed var(--tud-color-border-default);
  font-size: var(--tud-text-sm);
  color: var(--tud-color-text-secondary);
}
/* Two steps means two columns; a three column grid would leave a hole that
   reads as a missing card. */
.installed-steps { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.installed-editor { margin-top: var(--tud-space-4); }
.installed-steps .fine { margin-top: var(--tud-space-3); }

/* ---------- Pass check page ---------- */

.check-main { padding-inline: var(--tud-space-6); }
.check-card {
  width: min(100%, 700px);
  min-height: 490px;
  padding: clamp(var(--tud-space-8), 7vw, var(--tud-space-16));
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  border-radius: var(--tud-radius-xl);
  text-align: center;
  box-shadow: var(--tud-elevation-e3);
}
.check-card.is-loading, .check-card.is-error {
  color: var(--tud-color-text-primary);
  background: var(--tud-color-bg-surface);
  border: var(--tud-border-default) solid var(--tud-color-border-default);
}
.check-card.is-valid { color: var(--tud-color-scanner-valid-fg); background: var(--tud-color-scanner-valid-bg); }
.check-card.is-invalid { color: var(--tud-color-scanner-invalid-fg); background: var(--tud-color-scanner-invalid-bg); }
.check-card.is-expired { color: var(--tud-color-scanner-expired-fg); background: var(--tud-color-scanner-expired-bg); }
.check-badge {
  width: 112px;
  height: 112px;
  margin-bottom: var(--tud-space-6);
  display: grid;
  place-items: center;
  border-radius: var(--tud-radius-pill);
}
.check-card.is-valid .check-badge { color: var(--tud-color-ramp-neutral-0); background: var(--tud-color-ramp-neutral-950); }
.check-card.is-invalid .check-badge, .check-card.is-expired .check-badge { color: var(--tud-color-ramp-neutral-0); background: rgba(255,255,255,0.18); }
.check-overline { margin-bottom: var(--tud-space-3); color: inherit; }
.check-name { max-width: 14ch; font-size: clamp(40px, 7vw, 64px); line-height: 0.98; letter-spacing: -0.045em; }
.check-university { margin-top: var(--tud-space-4); font-size: clamp(20px, 3vw, 26px); font-weight: 800; line-height: 1.25; }
.check-detail { max-width: 37ch; margin-top: var(--tud-space-4); font-size: 18px; line-height: 1.55; }
.check-valid-until {
  margin-top: var(--tud-space-6);
  padding: var(--tud-space-2) var(--tud-space-4);
  border: var(--tud-border-hairline) solid currentColor;
  border-radius: var(--tud-radius-pill);
  font-family: var(--tud-font-secondary);
  font-size: 15px;
  font-weight: 700;
}
.check-loading-text { color: var(--tud-color-text-muted); font-size: 18px; }

/* ---------- Deals page ---------- */

.deals-header { max-width: 62ch; margin-bottom: var(--tud-space-8); }
.deals-header .page-title { margin-top: var(--tud-space-3); max-width: 18ch; }
.deals-header .lede { margin-top: var(--tud-space-4); }

.deal-pills {
  display: flex;
  flex-wrap: wrap;
  gap: var(--tud-space-3);
  margin-bottom: var(--tud-space-8);
}

.deal-pill {
  display: inline-flex;
  min-height: 44px;
  align-items: center;
  gap: var(--tud-space-2);
  padding: var(--tud-space-2) var(--tud-space-4);
  border: var(--tud-border-default) solid var(--tud-color-border-strong);
  border-radius: var(--tud-radius-pill);
  color: var(--tud-color-text-secondary);
  background: var(--tud-color-bg-surface);
  font-family: var(--tud-font-primary);
  font-size: 14px;
  font-weight: 800;
  cursor: pointer;
  text-decoration: none;
  transition:
    background-color var(--tud-duration-fast) var(--tud-ease-standard),
    border-color var(--tud-duration-fast) var(--tud-ease-standard),
    color var(--tud-duration-fast) var(--tud-ease-standard);
}

.deal-pill:hover { border-color: var(--tud-color-text-muted); }
.deal-pill[aria-pressed="true"] { border-color: var(--tud-color-accent-base); color: var(--tud-color-text-on-accent); background: var(--tud-color-accent-base); }
.deal-pill-icon { width: 24px; height: 24px; }
.deal-pill-icon img { width: 100%; height: 100%; object-fit: contain; }

/* ---------- Deals: loading skeleton ---------- */

.deals-loading { display: grid; gap: var(--tud-space-6); }

.skeleton-hero {
  display: grid;
  grid-template-columns: minmax(0, 0.86fr) minmax(0, 1.14fr);
  gap: var(--tud-space-8);
  padding: var(--tud-space-8);
  border: var(--tud-border-default) solid var(--tud-color-border-default);
  border-radius: var(--tud-radius-xl);
  background: var(--tud-color-bg-surface);
}

.skeleton-hero-visual { min-height: 260px; border-radius: var(--tud-radius-lg); }
.skeleton-hero-body { display: grid; align-content: center; gap: var(--tud-space-3); }
.skeleton-line { height: 16px; border-radius: var(--tud-radius-sm); }
.skeleton-line--sm { width: 30%; height: 12px; }
.skeleton-line--md { width: 70%; }
.skeleton-line--lg { width: 55%; height: 32px; }

.skeleton-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--tud-space-4); }
.skeleton-card {
  display: flex;
  gap: var(--tud-space-4);
  align-items: center;
  padding: var(--tud-space-4);
  border: var(--tud-border-default) solid var(--tud-color-border-default);
  border-radius: var(--tud-radius-lg);
  background: var(--tud-color-bg-surface);
}
.skeleton-card-panel { width: 96px; height: 96px; flex: 0 0 auto; border-radius: var(--tud-radius-md); }
.skeleton-card-lines { flex: 1; display: grid; gap: var(--tud-space-2); min-width: 0; }
.skeleton-block { background: var(--tud-color-bg-sunken); }

@media (prefers-reduced-motion: no-preference) {
  .skeleton-block { animation: skeleton-pulse 1.6s var(--tud-ease-standard) infinite; }
  @keyframes skeleton-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.5; }
  }
}

@media (prefers-reduced-motion: reduce) {
  .skeleton-block { opacity: 0.75; }
}

/* ---------- Deals: error / empty states ---------- */

.deals-state {
  display: grid;
  justify-items: center;
  gap: var(--tud-space-3);
  padding: var(--tud-space-16) var(--tud-space-6);
  border: var(--tud-border-default) dashed var(--tud-color-border-strong);
  border-radius: var(--tud-radius-xl);
  color: var(--tud-color-text-secondary);
  text-align: center;
}
/* An empty result is the one screen with nothing of its own to show, so it is
   the screen that needs the drawing most. The dashed box already centres its
   children, so the art only has to declare a size. */
.state-art { display: block; width: clamp(112px, 18vw, 156px); margin-bottom: var(--tud-space-2); }
.state-art img { display: block; width: 100%; height: auto; }

.deals-state h2 { max-width: 24ch; font-size: 22px; line-height: 1.25; color: var(--tud-color-text-primary); }
.deals-state p { max-width: 46ch; }
.deals-state .btn { margin-top: var(--tud-space-2); }
.deals-state--inline { padding: var(--tud-space-12) var(--tud-space-6); }

/* ---------- Deals: feed ---------- */

.deal-panel {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  display: grid;
  place-items: center;
  background: var(--tud-color-bg-sunken);
}
.deal-panel img { width: 100%; height: 100%; object-fit: cover; }
.deal-panel--doodle { padding: var(--tud-space-4); }
.deal-panel--doodle img { width: 68%; height: 68%; object-fit: contain; }
.deal-panel--doodle .theme-art { display: grid; place-items: center; width: 100%; height: 100%; }
.deal-panel--0 { background: var(--tud-color-deal-panel-0); }
.deal-panel--1 { background: var(--tud-color-deal-panel-1); }
.deal-panel--2 { background: var(--tud-color-deal-panel-2); }
.deal-panel--3 { background: var(--tud-color-deal-panel-3); }
.deal-panel--4 { background: var(--tud-color-deal-panel-4); }
.deal-panel--5 { background: var(--tud-color-deal-panel-5); }

.deal-badge {
  display: inline-flex;
  min-height: 24px;
  align-items: center;
  padding: 2px var(--tud-space-2);
  border-radius: var(--tud-radius-pill);
  font-family: var(--tud-font-secondary);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  line-height: 1;
  white-space: nowrap;
}
.deal-badge--exclusive { color: var(--tud-color-text-on-accent); background: var(--tud-color-accent-base); }
.deal-badge--urgent { color: var(--site-accent-text); background: var(--tud-color-accent-tint); border: var(--tud-border-hairline) solid var(--tud-color-accent-tint-border); }
.deal-badge--new { color: var(--tud-color-text-secondary); background: var(--tud-color-bg-sunken); border: var(--tud-border-hairline) solid var(--tud-color-border-strong); }

.deal-hero-card {
  display: grid;
  grid-template-columns: minmax(0, 0.86fr) minmax(0, 1.14fr);
  gap: var(--tud-space-8);
  padding: var(--tud-space-8);
  text-decoration: none;
  color: inherit;
  transition: transform var(--tud-duration-fast) var(--tud-ease-standard), box-shadow var(--tud-duration-fast) var(--tud-ease-standard);
}
.deal-hero-card:hover { transform: translateY(-2px); box-shadow: var(--tud-elevation-e2); }
.deal-hero-visual { position: relative; min-height: 260px; border-radius: var(--tud-radius-lg); overflow: hidden; }
.deal-hero-badges { position: absolute; top: var(--tud-space-4); left: var(--tud-space-4); z-index: 1; display: flex; gap: var(--tud-space-2); }
.deal-hero-body { display: grid; align-content: center; gap: var(--tud-space-2); min-width: 0; }
.deal-hero-brand { color: var(--tud-color-text-muted); font-size: 14px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.06em; }
.deal-hero-discount { color: var(--site-accent-text); font-family: var(--tud-font-secondary); font-size: clamp(28px, 3.6vw, 40px); font-weight: 700; line-height: 1.05; }
.deal-hero-title { max-width: 36ch; color: var(--tud-color-text-secondary); font-size: 18px; line-height: 1.4; }
.deal-hero-cta { margin-top: var(--tud-space-3); justify-self: start; }

.deals-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--tud-space-4); margin-top: var(--tud-space-5); }
.deal-card {
  display: flex;
  gap: var(--tud-space-4);
  align-items: center;
  padding: var(--tud-space-4);
  text-decoration: none;
  color: inherit;
  transition: transform var(--tud-duration-fast) var(--tud-ease-standard), box-shadow var(--tud-duration-fast) var(--tud-ease-standard);
}
.deal-card:hover { transform: translateY(-2px); box-shadow: var(--tud-elevation-e2); }
.deal-card-panel { width: 96px; height: 96px; flex: 0 0 auto; border-radius: var(--tud-radius-md); }
.deal-card-body { min-width: 0; flex: 1; display: grid; gap: 2px; }
.deal-card-brand { color: var(--tud-color-text-muted); font-size: 12px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.05em; }
.deal-card-discount { color: var(--site-accent-text); font-family: var(--tud-font-secondary); font-size: 18px; font-weight: 700; }
.deal-card-title {
  color: var(--tud-color-text-secondary);
  font-size: 14px;
  line-height: 1.4;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
.deal-card-badges { display: flex; flex-wrap: wrap; gap: var(--tud-space-1); margin-top: 2px; }
.deal-card-cta { margin-top: 2px; color: var(--site-accent-text); font-size: 13px; font-weight: 800; }

/* Quiet metadata line: where the deal works, and what it is for. */
.deal-card-meta,
.deal-hero-meta {
  color: var(--tud-color-text-muted);
  font-family: var(--tud-font-secondary);
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}
.deal-card-meta { margin-top: 2px; font-size: 11px; }
.deal-hero-meta { margin-top: var(--tud-space-1); font-size: 12px; }
.deals-header-actions { display: flex; flex-wrap: wrap; gap: var(--tud-space-3); margin-top: var(--tud-space-6); }

.deals-directory { margin-block: clamp(var(--tud-space-10), 6vw, var(--tud-space-16)); }
.deals-directory-head { max-width: 760px; }
.deals-directory-head .brand-section-title { margin-block: var(--tud-space-2) var(--tud-space-6); }
.deals-brand-links { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--tud-space-3); }
.deals-brand-link { display: grid; gap: var(--tud-space-1); padding: var(--tud-space-4); color: inherit; text-decoration: none; }
.deals-brand-link:hover { border-color: var(--tud-color-accent-tint-border); box-shadow: var(--tud-elevation-e2); transform: translateY(-2px); }
.deals-brand-link strong { font-size: 16px; line-height: 1.25; }
.deals-brand-link span { color: var(--site-accent-text); font-family: var(--tud-font-secondary); font-size: 13px; font-weight: 700; }

/* ---------- Brand pages (/deals/<slug>/) ---------- */

.brand-crumbs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--tud-space-2);
  margin-bottom: var(--tud-space-6);
  color: var(--tud-color-text-muted);
  font-size: 13px;
  font-weight: 700;
}
.brand-crumbs a { color: var(--tud-color-text-secondary); text-decoration: none; }
.brand-crumbs a:hover { color: var(--site-accent-text); text-decoration: underline; }
.brand-crumbs span[aria-hidden] { color: var(--tud-color-border-strong); }

/* The masthead closes on a hairline and the offers section sits close under it:
   the two are one unit, and the wide section rhythm starts after them. */
.brand-masthead {
  display: grid;
  grid-template-columns: minmax(0, 1.3fr) minmax(180px, 0.7fr);
  align-items: center;
  gap: clamp(var(--tud-space-6), 5vw, 64px);
  padding-bottom: clamp(var(--tud-space-8), 4vw, var(--tud-space-12));
  border-bottom: var(--tud-border-hairline) solid var(--tud-color-border-default);
}
.brand-mast-copy .page-title { max-width: 15ch; }
.brand-mast-copy .lede { margin-top: var(--tud-space-4); max-width: 46ch; }
.brand-mast-detail { max-width: 54ch; margin-top: var(--tud-space-3); color: var(--tud-color-text-secondary); }
.brand-mast-panel {
  width: 100%;
  max-width: 264px;
  aspect-ratio: 1;
  height: auto;
  margin-inline: auto;
  border-radius: var(--tud-radius-xl);
}

.brand-chips { display: flex; flex-wrap: wrap; gap: var(--tud-space-2); margin-top: var(--tud-space-6); }
.brand-chip {
  display: inline-flex;
  min-height: 30px;
  align-items: center;
  padding: 0 var(--tud-space-3);
  border: var(--tud-border-hairline) solid var(--tud-color-border-default);
  border-radius: var(--tud-radius-pill);
  color: var(--tud-color-text-muted);
  background: transparent;
  font-family: var(--tud-font-secondary);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
}
a.brand-chip { min-height: 44px; text-decoration: none; }
a.brand-chip:hover { border-color: var(--tud-color-accent-tint-border); color: var(--site-accent-text); }
.brand-chip--accent {
  color: var(--site-accent-text);
  border-color: var(--tud-color-accent-tint-border);
  background: var(--tud-color-accent-tint);
}

.brand-section { margin-top: clamp(var(--tud-space-12), 6.5vw, 84px); }
.brand-masthead + .brand-section { margin-top: clamp(var(--tud-space-8), 4vw, var(--tud-space-12)); }
.brand-section-title {
  margin-bottom: var(--tud-space-6);
  font-size: clamp(24px, 3vw, 32px);
  line-height: 1.12;
  letter-spacing: -0.03em;
}
.brand-section-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--tud-space-4) var(--tud-space-6);
  margin-bottom: var(--tud-space-6);
}
.brand-section-head .brand-section-title { margin-bottom: 0; }

/* EN / Urdu pill pair. Hidden until the head script marks the document as
   scripted, because without JavaScript there is nothing for it to switch. */
.lang-toggle {
  display: none;
  gap: 2px;
  padding: 3px;
  border: var(--tud-border-hairline) solid var(--tud-color-border-default);
  border-radius: var(--tud-radius-pill);
  background: var(--tud-color-bg-sunken);
}
[data-js="on"] .lang-toggle { display: inline-flex; }
.lang-btn {
  display: inline-flex;
  min-height: 32px;
  align-items: center;
  padding: 0 var(--tud-space-4);
  border: 0;
  border-radius: var(--tud-radius-pill);
  color: var(--tud-color-text-muted);
  background: transparent;
  font-family: var(--tud-font-secondary);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.12em;
  line-height: 1;
  text-transform: uppercase;
  cursor: pointer;
  transition:
    color var(--tud-duration-fast) var(--tud-ease-standard),
    background-color var(--tud-duration-fast) var(--tud-ease-standard);
}
.lang-btn:hover { color: var(--tud-color-text-primary); }
.lang-btn:focus-visible { outline-offset: 1px; }
.lang-btn[aria-pressed="true"] {
  color: var(--tud-color-text-on-accent);
  background: var(--tud-color-accent-base);
}
/* The default ring is accent, which disappears against the selected pill's own
   fill. Ink inside the fill is the system's other half and stays legible. */
.lang-btn[aria-pressed="true"]:focus-visible {
  outline-color: var(--tud-color-text-on-accent);
  outline-width: var(--tud-border-strong);
  outline-offset: -4px;
}
.lang-btn--ur {
  font-family: var(--site-font-urdu);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
}
/* Noto Sans Arabic carries deep descent metrics, so inside the pill's
   flex-centered line box the wordform's visual mass lands below center.
   The nudge rides the inner span: on the button it would shove the pressed
   state's own pill fill out of the track. Optical, judged by screenshot. */
.lang-btn--ur { line-height: 1; }
.lang-btn-glyphs { display: inline-block; transform: translateY(-5px); }

/* A brand's offers are a price list, so the saving is the largest thing in the
   row, the action sits in its own right-hand column like a line item, and the
   headline deal (always first) is set one size up. */
.brand-offers { display: grid; gap: var(--tud-space-4); }
.brand-offer {
  --offer-panel: 148px;
  display: grid;
  grid-template-columns: var(--offer-panel) minmax(0, 1fr) auto;
  align-items: center;
  column-gap: clamp(var(--tud-space-5), 2.6vw, var(--tud-space-8));
  row-gap: var(--tud-space-5);
  padding: clamp(var(--tud-space-5), 2.2vw, var(--tud-space-8));
  border-radius: var(--tud-radius-xl);
}
.brand-offer--lead { --offer-panel: 188px; }
.brand-offer-panel {
  width: var(--offer-panel);
  height: var(--offer-panel);
  border-radius: var(--tud-radius-lg);
}
.brand-offer-body { min-width: 0; display: grid; justify-items: start; gap: 0; }
.brand-offer-badges { display: flex; flex-wrap: wrap; gap: var(--tud-space-2); margin-bottom: var(--tud-space-3); }
.brand-offer-discount {
  color: var(--site-accent-text);
  font-family: var(--tud-font-secondary);
  font-size: clamp(28px, 3.6vw, 38px);
  font-weight: 700;
  line-height: 1.02;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}
.brand-offer--lead .brand-offer-discount { font-size: clamp(32px, 4.4vw, 46px); }
.brand-offer-title {
  margin-top: var(--tud-space-2);
  font-size: 18px;
  font-weight: 800;
  line-height: 1.3;
  letter-spacing: -0.015em;
}
.brand-offer--lead .brand-offer-title { font-size: 20px; }
.brand-offer-desc {
  max-width: 58ch;
  margin-top: var(--tud-space-2);
  color: var(--tud-color-text-secondary);
  font-size: 15px;
  line-height: 1.6;
}
.brand-offer-meta {
  margin-top: var(--tud-space-4);
  color: var(--tud-color-text-muted);
  font-family: var(--tud-font-secondary);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}
.brand-offer-action { display: grid; align-content: center; }
.brand-offer-cta { white-space: nowrap; }

/* The revealed code, drawn as the coupon it is: a dashed edge is the one
   border on the site that means "this part is yours to take". The value takes
   the label face at a size that can be read off a friend's screen, and the
   copy button rides inside the plate so code and control travel as one. */
.deal-code {
  display: flex;
  align-items: center;
  gap: var(--tud-space-3);
  padding: var(--tud-space-2);
  padding-left: var(--tud-space-4);
  border: 2px dashed var(--tud-color-border-strong);
  border-radius: var(--tud-radius-md);
  background: var(--tud-color-bg-surface);
}
.deal-code-body { min-width: 0; }
.deal-code-label {
  display: block;
  color: var(--tud-color-text-muted);
  font-family: var(--tud-font-secondary);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}
.deal-code-value {
  display: block;
  color: var(--tud-color-text-primary);
  font-family: var(--tud-font-secondary);
  font-size: 17px;
  font-weight: 800;
  letter-spacing: 0.05em;
  overflow-wrap: anywhere;
}
/* Pinned to the plate's far edge: on desktop the plate hugs its content so
   this is invisible, and on mobile, where the plate runs full width, the
   button holds the tearing corner instead of floating mid-coupon. */
.deal-code-copy { flex: none; margin-left: auto; }
.deal-code-use {
  display: inline-flex;
  margin-top: var(--tud-space-2);
  color: var(--tud-color-text-secondary);
  font-size: 14px;
  font-weight: 700;
}
.deal-code-use:hover { color: var(--tud-color-text-primary); }
/* A store-minted code opens the store with the discount applied, so the
   button leads and the coupon below it shrinks to a backup: same dashed edge,
   a smaller value, still copyable. */
.deal-code-shop { justify-self: start; margin-bottom: var(--tud-space-2); }
.deal-code-small { padding-left: var(--tud-space-3); border-width: 1px; }
.deal-code-small .deal-code-value { font-size: 14px; }
.deal-code-expiry {
  margin-top: var(--tud-space-1);
  color: var(--tud-color-text-muted);
  font-size: 13px;
  line-height: 1.4;
}
.deal-code-note {
  margin-top: var(--tud-space-2);
  color: var(--tud-color-status-warning-text);
  font-size: 14px;
  line-height: 1.4;
}

/* The Urdu band. Off unless the visitor asks for it. A rule across the body
   column carries the switch of script, so the right-aligned block reads as a
   deliberate second half of the row rather than as text that drifted right. */
.brand-offer-ur {
  display: none;
  justify-self: stretch;
  width: 100%;
  margin-top: var(--tud-space-4);
  padding-top: var(--tud-space-4);
  border-top: var(--tud-border-hairline) solid var(--tud-color-border-default);
}
[data-offer-lang="ur"] .brand-offer-ur { display: block; }
.brand-offer-title-ur {
  color: var(--tud-color-text-primary);
  font-size: 19px;
  font-weight: 600;
  line-height: 1.85;
}
.brand-offer-desc-ur {
  margin-top: var(--tud-space-1);
  color: var(--tud-color-text-secondary);
  font-size: 16px;
  font-weight: 400;
  line-height: 1.95;
}

/* Redemption is a genuine sequence, which is why these carry numbers. */
.redeem-steps {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(236px, 1fr));
  gap: var(--tud-space-4);
  margin: 0;
  padding: 0;
  list-style: none;
}
.redeem-step {
  padding: var(--tud-space-6);
  border: var(--tud-border-default) solid var(--tud-color-border-default);
  border-radius: var(--tud-radius-lg);
  background: var(--tud-color-bg-surface);
  color: var(--tud-color-text-secondary);
  font-size: 15px;
  line-height: 1.6;
}
.redeem-step-n {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  margin-bottom: var(--tud-space-5);
  border-radius: var(--tud-radius-sm);
  color: var(--tud-color-text-on-accent);
  background: var(--tud-color-accent-base);
  font-family: var(--tud-font-secondary);
  font-size: 14px;
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.redeem-step strong {
  display: block;
  margin-bottom: var(--tud-space-2);
  color: var(--tud-color-text-primary);
  font-size: 17px;
  line-height: 1.3;
  letter-spacing: -0.012em;
}

.brand-cta {
  position: relative;
  overflow: hidden;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: end;
  gap: var(--tud-space-10);
  padding: clamp(var(--tud-space-8), 6vw, var(--tud-space-16));
  border-radius: var(--tud-radius-xl);
  color: var(--tud-color-text-inverse);
  background: var(--tud-color-bg-inverse);
}
/* One ring, anchored into the top-right corner so the visible quarter reads as
   deliberate geometry rather than a shape that happens to be cropped. */
.brand-cta::after {
  position: absolute;
  top: -116px;
  right: -116px;
  width: 288px;
  height: 288px;
  border: 44px solid var(--tud-color-accent-base);
  border-radius: var(--tud-radius-pill);
  content: "";
  opacity: 0.42;
}
.brand-cta-copy { position: relative; z-index: 1; }
.brand-cta .overline { color: var(--tud-color-accent-base); }
.brand-cta h2 { max-width: 16ch; font-size: clamp(28px, 4vw, 42px); line-height: 1.04; letter-spacing: -0.035em; }
/* The panel inverts with the theme, so the body copy has to ride on the
   inverse text colour rather than a fixed neutral, or it vanishes in dark. */
.brand-cta p { max-width: 56ch; margin-top: var(--tud-space-4); color: var(--tud-color-text-inverse); opacity: 0.74; }
.brand-cta .btn { position: relative; z-index: 1; }

/* auto-fit, so five brands fill the row instead of leaving a sixth track empty. */
.brand-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(148px, 1fr)); gap: var(--tud-space-3); }
.brand-tile {
  display: grid;
  align-content: start;
  gap: var(--tud-space-3);
  padding: var(--tud-space-3);
  color: inherit;
  text-decoration: none;
  transition:
    transform var(--tud-duration-fast) var(--tud-ease-standard),
    border-color var(--tud-duration-fast) var(--tud-ease-standard),
    box-shadow var(--tud-duration-fast) var(--tud-ease-standard);
}
.brand-tile:hover {
  transform: translateY(-2px);
  border-color: var(--tud-color-accent-tint-border);
  box-shadow: var(--tud-elevation-e2);
}
.brand-tile-panel { width: 100%; height: auto; aspect-ratio: 4 / 3; border-radius: var(--tud-radius-md); }
.brand-tile-name { padding-inline: var(--tud-space-1); font-size: 14px; font-weight: 800; line-height: 1.25; }
.brand-tile-discount {
  margin-top: calc(var(--tud-space-2) * -1 + 2px);
  padding-inline: var(--tud-space-1);
  padding-bottom: var(--tud-space-1);
  color: var(--site-accent-text);
  font-family: var(--tud-font-secondary);
  font-size: 13px;
  font-weight: 700;
}

.brand-about p { max-width: 68ch; margin-bottom: var(--tud-space-5); color: var(--tud-color-text-secondary); line-height: 1.72; }
.brand-about p:first-of-type { color: var(--tud-color-text-primary); font-size: 17px; line-height: 1.66; }
.brand-about p:last-child { margin-bottom: 0; }

/* ---------- Category pages (/deals/<category>/) ---------- */

.category-masthead {
  display: flex;
  align-items: end;
  justify-content: space-between;
  gap: var(--tud-space-8);
  padding-bottom: clamp(var(--tud-space-8), 4vw, var(--tud-space-12));
  border-bottom: var(--tud-border-hairline) solid var(--tud-color-border-default);
}
.category-masthead .page-title { max-width: 17ch; }
.category-masthead .lede { max-width: 64ch; margin-top: var(--tud-space-4); }
.category-masthead > .brand-chip { flex: 0 0 auto; }
.category-offers { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--tud-space-4); }
.category-offer { display: grid; grid-template-rows: 180px 1fr; padding: var(--tud-space-3); color: inherit; text-decoration: none; }
.category-offer:hover { border-color: var(--tud-color-accent-tint-border); box-shadow: var(--tud-elevation-e2); transform: translateY(-2px); }
.category-offer-panel { border-radius: var(--tud-radius-md); }
.category-offer-body { display: grid; align-content: start; gap: var(--tud-space-2); padding: var(--tud-space-4) var(--tud-space-2) var(--tud-space-2); }
.category-offer-brand { font-size: 17px; line-height: 1.25; }
.category-offer-discount { color: var(--site-accent-text); font-family: var(--tud-font-secondary); font-size: 23px; font-weight: 700; line-height: 1.1; }
.category-offer-title { color: var(--tud-color-text-secondary); font-size: 14px; line-height: 1.45; }
.category-offer-cta { margin-top: var(--tud-space-2); font-size: 14px; font-weight: 800; text-decoration: underline; text-underline-offset: 3px; }
.category-siblings { display: flex; flex-wrap: wrap; gap: var(--tud-space-2); }
.category-empty h3 { color: var(--tud-color-text-primary); font-size: 22px; }

.brand-missing { max-width: 62ch; }
/* This block is set left, not centred like .deals-state, so the drawing leads
   the column and needs the eyebrow pushed clear of it. */
.brand-missing .state-art { margin-bottom: var(--tud-space-6); }
.brand-missing .page-title { max-width: 20ch; margin-top: var(--tud-space-3); }
.brand-missing .lede { margin-top: var(--tud-space-4); }
.brand-missing-actions { display: flex; flex-wrap: wrap; gap: var(--tud-space-3); margin-top: var(--tud-space-8); }

/* Logo, when a brand has one, sits over the doodle and removes itself if the
   file 404s so the panel never renders as a blank tile. */
.deal-panel-logo { position: absolute; inset: 12%; width: 76%; height: 76%; }
.deal-panel--doodle .deal-panel-logo { width: 76%; height: 76%; object-fit: contain; }

/* ---------- Motion ----------
   Rules for this file: transform and opacity only, nothing over 600ms, nothing
   loops, and every hidden-start state lives inside a no-preference query so the
   reduced-motion default is already the finished frame. */

/* Hand-drawn underline under the hero's accent words. The one authored moment
   on the page: the site's whole voice is marker on paper, so the headline gets
   marked the way a student would mark it. pathLength="1" in the markup makes
   the dash maths independent of the path's real geometry. */
.hero-underline path { stroke-dasharray: 1; stroke-dashoffset: 0; }
/* Same trick for the confirmation tick. Scoped to paths that opt in with the
   attribute, so status icons elsewhere on the site keep a solid stroke. */
.status-icon svg path[pathLength] { stroke-dasharray: 1; }

@media (prefers-reduced-motion: no-preference) {

  /* --- Hero load sequence: title, lede, form, then the doodle lands. --- */
  .hero-copy > .display-title,
  .hero-copy > .lede,
  .hero-copy > .join-card { animation: hero-rise 380ms var(--tud-ease-decelerate) both; }
  .hero-copy > .lede { animation-delay: 70ms; }
  .hero-copy > .join-card { animation-delay: 140ms; }
  /* The doodle arrives last and off-square, then settles onto its resting
     tilt. It is the only element that rotates in, because it is the only one
     that was drawn by hand. */
  .hero-doodle-stage { animation: hero-land 420ms var(--tud-ease-decelerate) 180ms both; }
  /* Standard rather than decelerate: a pen starts from rest, so the stroke
     needs the small ease-in that a pure decelerate curve does not have. */
  .hero-underline path { stroke-dashoffset: 1; animation: hero-underline-draw 400ms var(--tud-ease-standard) 300ms forwards; }

  @keyframes hero-rise {
    from { opacity: 0; transform: translateY(16px); }
    to { opacity: 1; transform: none; }
  }
  @keyframes hero-land {
    from { opacity: 0; transform: translateY(18px) rotate(2deg); }
    to { opacity: 1; transform: translateY(0) rotate(0deg); }
  }
  @keyframes hero-underline-draw {
    to { stroke-dashoffset: 0; }
  }

  /* --- Section reveals. JS arms only sections that start below the fold, so
     anything already on screen is never hidden and can never flash. --- */
  .is-reveal {
    opacity: 0;
    transform: translateY(14px);
    transition:
      opacity 420ms var(--tud-ease-decelerate),
      transform 420ms var(--tud-ease-decelerate);
  }
  .is-reveal.is-revealed { opacity: 1; transform: none; }

  /* Card grids arrive as a list, because they are one. Capped at five, so a
     long row never turns the reveal into a queue. */
  .is-reveal :is(.steps-grid, .cats-grid, .option-grid, .redeem-steps) > * {
    opacity: 0;
    transform: translateY(12px);
    transition:
      opacity 340ms var(--tud-ease-decelerate),
      transform 340ms var(--tud-ease-decelerate);
  }
  .is-reveal.is-revealed :is(.steps-grid, .cats-grid, .option-grid, .redeem-steps) > * { opacity: 1; transform: none; }
  .is-reveal.is-revealed :is(.steps-grid, .cats-grid, .option-grid, .redeem-steps) > *:nth-child(2) { transition-delay: 50ms; }
  .is-reveal.is-revealed :is(.steps-grid, .cats-grid, .option-grid, .redeem-steps) > *:nth-child(3) { transition-delay: 100ms; }
  .is-reveal.is-revealed :is(.steps-grid, .cats-grid, .option-grid, .redeem-steps) > *:nth-child(4) { transition-delay: 150ms; }
  .is-reveal.is-revealed :is(.steps-grid, .cats-grid, .option-grid, .redeem-steps) > *:nth-child(n+5) { transition-delay: 200ms; }

  /* The merchant side is the same language spoken more quietly. */
  .is-reveal--calm { transform: translateY(10px); transition-duration: 460ms; }
  .is-reveal--calm :is(.steps-grid, .cats-grid, .option-grid, .redeem-steps) > * { transform: translateY(8px); }

  /* --- Doodle personality. Hover only, so a tap never triggers it, and the
     same response on keyboard focus for anything that can be focused. --- */
  .cat-card .theme-art,
  .step-card .theme-art,
  .enforce-art,
  .legal-art,
  .installed-nod,
  .state-art,
  .deal-pill-icon img,
  :is(.deal-card, .deal-hero-card, .category-offer, .brand-tile) .deal-panel--doodle img {
    transition: transform var(--tud-duration-base) var(--tud-ease-standard);
  }

  /* --- Card steps. The step arriving is the only thing that moves, and it
     borrows the hero's own rise so the card keeps speaking one language. --- */
  #join-step-code:not(.hidden) { animation: hero-rise 260ms var(--tud-ease-decelerate) both; }

  /* --- Success moment on the signup and partner forms. --- */
  .success:not(.hidden),
  .partners-success:not(.hidden) { animation: success-pop 280ms var(--tud-ease-standard) both; }
  .success:not(.hidden) .status-icon svg path[pathLength],
  .partners-success:not(.hidden) .status-icon svg path[pathLength] {
    stroke-dashoffset: 1;
    animation: tick-draw 300ms var(--tud-ease-standard) 160ms both;
  }

  /* The settle lives in the keyframes, not in an elastic timing function: the
     card passes a hair beyond full size and eases back down. */
  @keyframes success-pop {
    0% { opacity: 0; transform: scale(0.96); }
    62% { opacity: 1; transform: scale(1.012); }
    100% { opacity: 1; transform: none; }
  }
  @keyframes tick-draw {
    to { stroke-dashoffset: 0; }
  }
}

@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .cat-card:hover .theme-art,
  .cat-card:focus-visible .theme-art { transform: rotate(-4deg) scale(1.06); }
  .step-card:hover .theme-art { transform: rotate(3deg) scale(1.04); }
  /* No card wraps this one, so the drawing answers to its own hover. */
  .enforce-art:hover { transform: rotate(-3deg) scale(1.04); }
  /* Same rule for the marks that stand alone on the legal, success and
     empty-state pages: each one is its own hover target. */
  .legal-art:hover { transform: rotate(-4deg) scale(1.05); }
  .installed-nod:hover { transform: rotate(5deg) scale(1.06); }
  .state-art:hover { transform: rotate(-3deg) scale(1.05); }
  .deal-pill:hover .deal-pill-icon img,
  .deal-pill:focus-visible .deal-pill-icon img { transform: rotate(-6deg); }
  :is(.deal-card, .deal-hero-card, .category-offer, .brand-tile):hover .deal-panel--doodle img,
  :is(.deal-card, .deal-hero-card, .category-offer, .brand-tile):focus-visible .deal-panel--doodle img { transform: rotate(-3deg) scale(1.05); }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; }
}

/* An armed section holds at opacity 0 until its observer fires, which is fine
   for a reader and wrong for anything that captures the page whole. Printing
   and full-page screenshots never scroll, so a section that has not been
   reached would come out blank. Both fail to visible instead. app.js also
   releases every armed section on a safety timer for the same reason. */
@media print {
  .is-reveal, .is-reveal :is(.steps-grid, .cats-grid, .option-grid, .redeem-steps) > * {
    opacity: 1 !important;
    transform: none !important;
  }
}

/* ---------- Responsive ---------- */

@media (max-width: 920px) {
  .nav-links { display: none; }
  .hero-layout { grid-template-columns: 1fr; }
  .hero-copy { max-width: 720px; }
  .hero-visual { min-height: auto; margin-top: var(--tud-space-6); }
  .hero-doodle-stage { width: min(100%, 520px); }
  /* The form needs real width for its two-up rows, which a shared track no
     longer has room to give it under 920px, so the hero stacks: pitch, its
     closing doodle, then the form beneath. */
  .partners-pitch { grid-template-columns: 1fr; gap: var(--tud-space-8); }
  .partners-pitch-copy { max-width: 640px; }
  /* The sign-in line was pinned into the card's own column on the wide grid;
     that column is gone now, so it falls back to the single track like
     everything else. */
  .partners-portal-signin { grid-column: 1; }
  .partners-benefits { margin-top: var(--tud-space-6); }
  /* Stacked, so there is no shared row to stretch against and no form foot to
     match. The drawing goes back to being a modest close to the column: a
     fixed spacing, not the auto margin that only makes sense beside the card,
     and centred under the text it closes rather than hugging its left edge. */
  .partners-art { display: flex; justify-content: center; margin-top: var(--tud-space-8); padding-top: 0; }
  .partners-art img { height: clamp(190px, 30vw, 260px); }
  /* Fallback heights only, replaced the moment the frame reports its own. The
     portal's layout gets taller as it narrows, so these grow the other way to
     the breakpoints above them. */
  .portal-demo-stage.is-demo-framed { height: 660px; }
  /* The poster stops being a column beside the ledger and becomes the object
     the last row points at, set at the width a sheet wants rather than full. */
  .portal-detail { grid-template-columns: 1fr; }
  .portal-plate--sheet { width: min(100%, 320px); }
  /* A 260px column beside the ledger stops being a column and starts being a
     squeeze. She drops under the rows and gets smaller, so she still signs the
     passage without heading it. */
  .enforce-layout { grid-template-columns: 1fr; gap: var(--tud-space-8); }
  .enforce-art { width: min(100%, 210px); margin-inline: auto; }
  .deal-hero-card, .skeleton-hero { grid-template-columns: 1fr; }
  .deals-grid, .skeleton-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .deals-brand-links, .category-offers { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .brand-masthead { grid-template-columns: 1fr; gap: var(--tud-space-8); }
  .brand-mast-copy .page-title { max-width: none; }
  .brand-mast-copy .lede { max-width: none; }
  /* Once the copy goes full width the panel has nothing to sit beside, and the
     same doodle is already carrying the offer row directly below. */
  .brand-mast-panel { display: none; }
  /* The action loses its own column and drops under the body copy it belongs to. */
  .brand-offer { grid-template-columns: var(--offer-panel) minmax(0, 1fr); }
  .brand-offer-action { grid-column: 2; justify-self: start; }
  .brand-cta { grid-template-columns: 1fr; align-items: start; gap: var(--tud-space-8); }
  .brand-cta .btn { justify-self: start; }
}

@media (max-width: 760px) {
  .installed-hero { grid-template-columns: 1fr; gap: var(--tud-space-8); }
  .installed-steps { grid-template-columns: 1fr; }
  .section-heading { display: block; margin-bottom: var(--tud-space-8); }
  .section-heading > p { margin-top: var(--tud-space-3); }
  .steps-grid { grid-template-columns: 1fr; }
  .step-card { display: grid; grid-template-columns: 136px minmax(0, 1fr); grid-template-rows: auto auto; align-items: center; column-gap: var(--tud-space-5); }
  .step-card .theme-art { grid-row: 1 / span 2; min-height: 120px; margin: 0; }
  .step-card img { height: 116px; }
  .step-card h3 { align-self: end; }
  .step-card p { align-self: start; }
  .cats-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .cat-card:last-child { grid-column: 1 / -1; }
  .merchant-card { grid-template-columns: 1fr; align-items: start; }
  .merchant-card .btn { justify-self: start; }
  .faq-layout { grid-template-columns: 1fr; gap: var(--tud-space-8); }
  .faq-intro { position: static; }
  .footer-utility { grid-template-columns: 1fr; gap: var(--tud-space-8); }
  .footer-tagline { max-width: 360px; }
  .deal-hero-card { padding: var(--tud-space-6); gap: var(--tud-space-5); }
  .deal-hero-visual { min-height: 200px; }
  .deals-grid, .skeleton-grid { grid-template-columns: 1fr; }
  .category-masthead { display: grid; align-items: start; gap: var(--tud-space-5); }
  .category-masthead > .brand-chip { justify-self: start; }
  .deal-card { align-items: flex-start; }
  .brand-offer { grid-template-columns: 1fr; row-gap: var(--tud-space-5); }
  /* Full-width 16/9 is right at phone widths but turns into a 370px slab just
     under the breakpoint. Capped on width, not height: the doodle inside is
     sized in percentages and a max-height leaves it overflowing. */
  .brand-offer-panel { width: 100%; max-width: 440px; height: auto; aspect-ratio: 16 / 9; }
  .brand-offer-action { grid-column: auto; }
  .option-grid { grid-template-columns: 1fr; }
  .enforce-row { grid-template-columns: 1fr; }
  /* Two-column step-cards put the heading on the same row as the step number,
     and the go-live headings are long enough to reach it. The heading gives up
     the strip the number sits in rather than running under it. */
  .setup-steps .step-card h3 { padding-right: var(--tud-space-8); }
  /* The state and its button drop under the description they belong to and pair
     up on one line, so the control still sits beside the word it acts on. */
  .legal .storage-live-row { grid-template-columns: minmax(0, 1fr) auto; }
  .storage-live-copy { grid-column: 1 / -1; }
  .storage-live-state { padding-top: 0; align-self: center; }
}

@media (max-width: 560px) {
  .container, .nav, .footer-utility, .legal { width: min(100% - (var(--tud-space-4) * 2), var(--site-content)); }
  /* It keeps floating on a phone rather than dropping into the column, because
     the drawing sits above the passage in source order and a stacked copy would
     wedge itself between the eyebrow and the heading. It just takes less room. */
  .legal-art { width: 96px; margin-left: var(--tud-space-4); }
  .nav { min-height: 72px; gap: var(--tud-space-2); }
  .nav-logo { height: 27px; }
  .nav-right { gap: var(--tud-space-2); }
  .nav-cta { min-height: 44px; padding-inline: var(--tud-space-3); }
  .display-title { font-size: clamp(40px, 13vw, 58px); }
  .page-title { font-size: 38px; }
  .hero { padding-top: var(--tud-space-10); }
  .hero-copy .lede { margin-top: var(--tud-space-5); }
  .join-card { margin-top: var(--tud-space-6); padding: var(--tud-space-5); }
  .join-fields, .field-row { grid-template-columns: 1fr; }
  .success h2 { font-size: 26px; }
  /* Still a step above .partners-form-heading h2, which holds at 28px here:
     the heading you earn should not be smaller than the one that asked. */
  .partners-success h2 { font-size: 31px; }
  .success .referral-stat-value { font-size: 26px; }
  .hero-visual { margin-top: var(--tud-space-4); overflow: clip; }
  .hero-doodle-stage { width: min(100%, 440px); }
  .step-card { grid-template-columns: 96px minmax(0, 1fr); column-gap: var(--tud-space-4); padding: var(--tud-space-5); }
  .step-card .theme-art { min-height: 96px; }
  .step-card img { height: 90px; }
  .step-number { top: var(--tud-space-3); right: var(--tud-space-4); }
  .cat-card { min-height: 164px; }
  .cat-card img { height: 84px; }
  .merchant-card .btn { width: 100%; }
  .footer-utility { padding-top: var(--tud-space-12); gap: var(--tud-space-6); }
  .footer-tagline { max-width: 280px; font-size: 24px; }
  /* Two link columns side by side halve the footer's height on a phone;
     Company drops to a wrapping row under a hairline so the email keeps room. */
  .footer-columns { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--tud-space-6); row-gap: var(--tud-space-5); }
  .footer-column h2, .footer-social h2 { margin-bottom: var(--tud-space-1); }
  .footer-social { margin-top: var(--tud-space-3); }
  .footer-column:last-child { grid-column: 1 / -1; padding-top: var(--tud-space-4); border-top: var(--tud-border-hairline) solid var(--tud-color-border-default); }
  .footer-column:last-child .footer-links { display: flex; flex-wrap: wrap; column-gap: var(--tud-space-6); }
  .footer-landmark { margin-top: var(--tud-space-5); }
  .confirm-main, .check-main { padding-inline: var(--tud-space-4); }
  .confirm-card { padding: var(--tud-space-6) var(--tud-space-5); }
  .reward-art { width: 116px; }
  .referral-link-row { grid-template-columns: 1fr; }
  .referral-link-row .btn { width: 100%; }
  .referral-share { grid-template-columns: 1fr; }
  .referral-share .btn { width: 100%; }
  /* Still centred and still a real presence on a phone, just scaled to the
     narrower column: it is the one hand-drawn note in an otherwise all-text,
     all-form column, and earns the room it takes. */
  .partners-art { margin-top: var(--tud-space-6); }
  .partners-art img { height: clamp(150px, 42vw, 200px); }
  .partners-form-card { padding: var(--tud-space-6) var(--tud-space-5); }
  .portal-row { grid-template-columns: 1fr; gap: var(--tud-space-1); }
  .portal-demo-open { width: 100%; }
  /* A phone gets the live portal at its own width, not a shrunken desktop
     screenshot: the frame is its own viewport, so the portal's narrow-width
     rules fire at 390 and the merchant sees exactly what they will be holding.
     Nothing is scaled and nothing pans. The taller fallback is only for the
     seconds before the frame reports its real content height. */
  .portal-demo-stage.is-demo-framed { height: 780px; }
  .check-card { min-height: 430px; padding: var(--tud-space-8) var(--tud-space-5); }
  .check-badge { width: 96px; height: 96px; }
  .check-name { font-size: clamp(38px, 12vw, 52px); }
  .deals-header .page-title { max-width: none; }
  .deals-brand-links, .category-offers { grid-template-columns: 1fr; }
  .deal-pill { padding: var(--tud-space-2) var(--tud-space-3); font-size: 13px; }
  .deal-pill-icon { width: 20px; height: 20px; }
  .deal-hero-card { padding: var(--tud-space-5); }
  .deal-hero-visual { min-height: 168px; }
  .deal-hero-discount { font-size: clamp(26px, 9vw, 32px); }
  .deal-hero-cta { width: 100%; }
  .deal-card { padding: var(--tud-space-3); gap: var(--tud-space-3); }
  .deal-card-panel { width: 76px; height: 76px; }
  .skeleton-card-panel { width: 76px; height: 76px; }
  .skeleton-hero { padding: var(--tud-space-5); }
  .skeleton-hero-visual { min-height: 168px; }
  .deals-state { padding: var(--tud-space-10) var(--tud-space-4); }
  .brand-offer { padding: var(--tud-space-5); }
  /* justify-self: start comes down from the 920 rule; undo it so the action
     column can stretch and the button really goes full width. */
  .brand-offer-action { justify-self: stretch; }
  .brand-offer-cta { width: 100%; }
  .brand-cta .btn, .brand-missing-actions .btn { width: 100%; }
  .redeem-steps { grid-template-columns: 1fr; }
  .redeem-step { padding: var(--tud-space-5); }
}

@media (max-width: 390px) {
  .nav-cta { font-size: 13px; }
  .nav-logo { height: 25px; }
  .theme-toggle { width: 44px; height: 44px; }
  .footer-wordmark { font-size: 64px; }
}
