/* public-utilities.css — the utility classes the public pages already use.
 *
 * WHY THIS FILE EXISTS
 *
 * The public site's markup is written in Tailwind's vocabulary. The public
 * base template does not load Tailwind. `templates/SGT_Portfolio/base.html`
 * does — via `https://cdn.tailwindcss.com`, the Play CDN — so the logged-in
 * dashboard's utilities work and the public pages' do not. Nothing in the HTML
 * tells you which side of that line a template is on.
 *
 * The homepage alone carried 37 class names that resolved to no rule anywhere:
 * 77 occurrences, and among them EVERY responsive `md:` variant on the page.
 * The page's whole mobile layout was expressed in classes that did nothing,
 * which is why it does not adapt and why the file it lives in has 352
 * `!important` declarations — when half your utilities fail silently, the only
 * way forward is one more override.
 *
 * WHY NOT JUST LOAD TAILWIND
 *
 * Because `cdn.tailwindcss.com` is the Play CDN, which Tailwind documents as
 * not for production: it ships a compiler to the browser and builds the CSS
 * there, on every visit. Putting that on the most-visited page on the site, to
 * improve how it performs on phones, would be working against the goal. The
 * dashboard's use of it is a thing to fix later, not a pattern to copy.
 *
 * It would also change 77 things at once on a page nobody here can look at —
 * there is no browser on the machine this was written on.
 *
 * These names are Tailwind's own, deliberately. If the site later gets a real
 * compiled Tailwind build, this file is deleted and nothing in any template
 * has to change.
 *
 * RULES FOR ADDING TO THIS FILE
 *
 * - Only classes the markup already uses. This file follows the templates; it
 *   does not invent a vocabulary for them to adopt.
 * - Values are Tailwind's defaults, so a later migration is a deletion.
 * - No `!important`, with one exception, marked as such: the
 *   `prefers-reduced-motion` blanket at the foot of this file. It has to beat
 *   the 188 `!important` animation rules already in the page, and a rule that
 *   loses to them would be an accessibility promise that quietly does nothing.
 *   Every other rule here wins on ordinary specificity or does not belong.
 * - `scripts/check_dead_classes.py` fails if a public template uses a class
 *   that resolves nowhere. Add it here or remove it from the markup.
 */

/* ── the fluid type scale ─────────────────────────────────────────────
 *
 * The homepage had 26 distinct font sizes and one `clamp()`. 0.8rem, 0.85rem,
 * 0.875rem, 0.9rem and 0.95rem all appeared side by side; nobody chose those
 * differences, they accumulated. And because `rem` does not follow the
 * viewport and the `md:text-*` variants were dead, NO heading on the page
 * changed size between a 360px phone and a 2560px monitor. That is the whole
 * of "the type does not fit the screen", and this is its fix.
 *
 * Ten steps. Each interpolates from its phone size at 360px to its desktop
 * size at 1280px and then stops -- `clamp()` holds the ends, so a 320px phone
 * is not punished and a 4K monitor does not get 90px body text.
 *
 * The 26 sizes were mapped onto these by NEAREST MAXIMUM, deliberately. The
 * old static size is what a desktop reader sees today, so matching the step's
 * top keeps the wide layout roughly as it was and lets the phone come down --
 * which is the defect, not a side effect of fixing it.
 *
 * Generated arithmetic; the computed sizes below were produced by the same
 * script, not estimated. Regenerate rather than hand-edit a value.
 */
:root {
    --step--2:  clamp(0.7500rem, 0.7255rem + 0.1087vw, 0.8125rem);   /* caption, badge, table note */
    --step--1:  clamp(0.8750rem, 0.8505rem + 0.1087vw, 0.9375rem);   /* small print, helper text */
    --step-0:   clamp(1.0000rem, 0.9755rem + 0.1087vw, 1.0625rem);   /* body */
    --step-1:   clamp(1.1250rem, 1.0761rem + 0.2174vw, 1.2500rem);   /* lead paragraph, large label */
    --step-2:   clamp(1.2500rem, 1.1522rem + 0.4348vw, 1.5000rem);   /* h4 / card title */
    --step-3:   clamp(1.5000rem, 1.3533rem + 0.6522vw, 1.8750rem);   /* h3 */
    --step-4:   clamp(1.7500rem, 1.4565rem + 1.3043vw, 2.5000rem);   /* h2 / section heading */
    --step-5:   clamp(2.0000rem, 1.6087rem + 1.7391vw, 3.0000rem);   /* h1 */
    --step-6:   clamp(2.2500rem, 1.7609rem + 2.1739vw, 3.5000rem);   /* hero heading */
    --step-7:   clamp(2.5000rem, 1.5217rem + 4.3478vw, 5.0000rem);   /* the single display number */
}

/* computed size in px */
/* step          360px    768px   1280px */
/* step--2       12.0     12.4     13.0 */
/* step--1       14.0     14.4     15.0 */
/* step-0        16.0     16.4     17.0 */
/* step-1        18.0     18.9     20.0 */
/* step-2        20.0     21.8     24.0 */
/* step-3        24.0     26.7     30.0 */
/* step-4        28.0     33.3     40.0 */
/* step-5        32.0     39.1     48.0 */
/* step-6        36.0     44.9     56.0 */
/* step-7        40.0     57.7     80.0 */


/* ── spacing ─────────────────────────────────────────────────────────── */
.gap-6 { gap: 1.5rem; }
.mb-10 { margin-bottom: 2.5rem; }
.mb-20 { margin-bottom: 5rem; }
.mt-12 { margin-top: 3rem; }

/* `space-y-*` puts the gap BETWEEN children, so the last child adds no
 * trailing space. That is why it is not simply margin-bottom on each. */
.space-y-2 > * + * { margin-top: 0.5rem; }
.space-y-4 > * + * { margin-top: 1rem; }
.space-y-5 > * + * { margin-top: 1.25rem; }
.space-y-6 > * + * { margin-top: 1.5rem; }

/* ── layout ──────────────────────────────────────────────────────────── */
.inline-block { display: inline-block; }
.items-start { align-items: flex-start; }
.grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.max-w-2xl { max-width: 42rem; }
.max-w-3xl { max-width: 48rem; }

/* ── type ────────────────────────────────────────────────────────────── */
.font-normal { font-weight: 400; }
.tracking-tighter { letter-spacing: -0.05em; }
.tracking-tight { letter-spacing: -0.025em; }
.tracking-wide { letter-spacing: 0.025em; }
.decoration-amber-500\/30 { text-decoration-color: rgba(245, 158, 11, 0.3); }

/* ── dividers ────────────────────────────────────────────────────────── */
.divide-y > * + * { border-top-width: 1px; border-top-style: solid; }
.divide-white\/5 > * + * { border-color: rgba(255, 255, 255, 0.05); }

/* ── states ──────────────────────────────────────────────────────────── */
/* The card hover the markup has been asking for since it was written. */
.hover\:bg-white\/\[0\.02\]:hover { background-color: rgba(255, 255, 255, 0.02); }

/* ── motion ──────────────────────────────────────────────────────────── */
/* Defined outside the reduced-motion guard and switched off inside it, so the
 * default is the animation and the exception is documented in one place. The
 * page already honours this preference in one media query; everything added
 * from here on joins it. */
@keyframes sgt-pulse {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.5; }
}
.animate-pulse { animation: sgt-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; }

@media (prefers-reduced-motion: reduce) {
    .animate-pulse { animation: none; }
}

/* ── the responsive variants ─────────────────────────────────────────── */
/* This block is the point of the file. Every one of these was in the markup
 * and in no stylesheet, so the page rendered its phone layout at every width.
 * 768px is Tailwind's `md`, kept so the class names stay honest. */
@media (min-width: 768px) {
    .md\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .md\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    .md\:flex-row    { flex-direction: row; }
    /* Pointed at the scale rather than given flat sizes. The scale already
     * grows these headings from phone to desktop, so a second, static system
     * for the same job is how the two drift apart. The step maxima are the
     * sizes Tailwind's `text-5xl`/`text-6xl` would have given. */
    .md\:text-5xl    { font-size: var(--step-5); line-height: 1.05; }
    .md\:text-6xl    { font-size: var(--step-6); line-height: 1.05; }
    .md\:p-10        { padding: 2.5rem; }
    .md\:p-12        { padding: 3rem; }
    .md\:pl-10       { padding-left: 2.5rem; }
    .md\:mt-0        { margin-top: 0; }
    .md\:rotate-0    { transform: rotate(0deg); }
    .md\:w-1\/3      { width: 33.333333%; }
    .md\:w-2\/3      { width: 66.666667%; }
}

/* DELIBERATELY NOT DEFINED: `min-h-screen`.
 *
 * It appeared twice and did nothing, so nothing on the page depends on it.
 * Defining it as `100vh` would introduce the exact mobile bug this work is
 * meant to remove: on a phone the viewport height changes as the address bar
 * hides and shows, so a `100vh` section jumps while the reader scrolls, and a
 * short section is padded out to a full screen for no reason. The two uses are
 * removed from the markup instead. If a full-height section is ever wanted,
 * `100svh` is the unit, and it should be a considered choice rather than a
 * class that has been inert for years.
 */

/* ── keyboard focus ──────────────────────────────────────────────────────
 *
 * The homepage carries 27 `:hover` rules, two `:focus` rules and no
 * `:focus-visible` at all. Someone moving through the page with a keyboard --
 * because they prefer to, or because a mouse is not an option -- had almost no
 * way of telling where they were. Hover has been thought about twenty-seven
 * times and focus twice.
 *
 * `:focus-visible` rather than `:focus`, so this appears for keyboard
 * navigation and stays out of the way of a mouse click. Nothing a mouse user
 * does will look any different than it did.
 *
 * The outline sits OUTSIDE the element with an offset instead of replacing its
 * border, because a ring drawn on the border changes an element's colour and
 * can vanish against a background it happens to match. An offset ring is
 * visible on cream, on white and on gold alike.
 */
:where(a, button, [role="button"], input, select, textarea, summary,
       [tabindex]:not([tabindex="-1"])):focus-visible {
    outline: 3px solid #B08D1E;
    outline-offset: 3px;
    border-radius: 4px;
}

/* Where a control already draws its own rounded shape, match it so the ring
 * follows the button rather than boxing it. */
.btn:focus-visible,
.trust-badge:focus-visible {
    border-radius: inherit;
}

/* ── icon sizes ──────────────────────────────────────────────────────────
 *
 * Ten icons on the homepage carried `w-4 h-4`, four circles carried
 * `w-10 h-10`, and none of those four class names existed anywhere. The
 * checker had been waving them through because its Bootstrap pattern matched
 * `w-` and `h-` as bare prefixes -- Bootstrap only ships w-25/50/75/100/auto,
 * so every Tailwind size was reading as vendor-provided. Tightening that is
 * what surfaced these.
 *
 * It matters more than a wrong size. Lucide draws its 76 icons with
 * JavaScript, after the HTML is parsed. An `<i>` with no width is zero
 * pixels wide until the script runs, and then it is not -- so the line it
 * sits in reflows the moment the icons appear. Giving the box its size up
 * front means the space is already reserved and nothing moves.
 *
 * Values are Tailwind's, matching `.w-5`/`.h-5` already in the page at
 * 1.25rem.
 */
.w-2  { width: 0.5rem; }
.h-2  { height: 0.5rem; }
.w-4  { width: 1rem; }
.h-4  { height: 1rem; }
/* `.w-6` looked defined when this file was first written: a grep for `.w-6`
 * matched `.w-64` further down the page. The checker, which compares whole
 * names, was not fooled. */
.w-6  { width: 1.5rem; }
.h-6  { height: 1.5rem; }
.w-10 { width: 2.5rem; }
.h-10 { height: 2.5rem; }

/* Every Lucide placeholder, sized or not, holds its line before the script
 * replaces it. `data-lucide` is on the element from the server, so this
 * applies from the first paint. */
[data-lucide] {
    display: inline-block;
    width: 1em;
    height: 1em;
    flex-shrink: 0;
}

/* ── motion, for people who have asked for less of it ────────────────────
 *
 * The homepage honoured `prefers-reduced-motion` in exactly one rule, for one
 * element's text shimmer, while 37 scroll reveals, 13 CSS animations and 24
 * transitions ignored it. For someone with vestibular sensitivity the shimmer
 * was never the problem; a page where thirty-seven things slide in as you
 * scroll is.
 *
 * The blanket below sets durations to almost-zero rather than to `none`,
 * which is deliberate. `animation: none` cancels an animation mid-way and can
 * leave an element at whatever value it was interpolating -- collapsed,
 * transparent, off-screen. A 0.01ms duration runs the animation to completion
 * instantly, so every element lands where it was going, just without the
 * journey. `iteration-count: 1` stops anything infinite from spinning forever
 * at that speed.
 *
 * `scroll-behavior: auto` matters as much as the rest: the page's own anchor
 * links smooth-scroll, and a smooth scroll across a long page is motion the
 * reader did not ask for either.
 *
 * AOS is disabled in its own init rather than here -- see the comment there.
 * Turning it off in CSS would leave its elements at the opacity:0 it starts
 * from and never reveal them.
 */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

/* ── the gold-education tabs ─────────────────────────────────────────────
 *
 * The strip scrolls sideways instead of wrapping. Four or five labels do not
 * fit across a 360px phone, and a wrapped strip becomes two ragged rows that
 * read like a rendering fault rather than a choice. One row that scrolls, with
 * the next tab's edge visible, is what tells a reader there is more.
 *
 * `scrollbar-width: none` hides the bar without disabling the scroll -- the
 * peeking tab is the affordance here, and a scrollbar under four buttons looks
 * broken on a desktop where they all fit anyway.
 */
.sgt-learn-tabs {
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    justify-content: flex-start;
    gap: 0.5rem;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
}

.sgt-learn-tabs::-webkit-scrollbar { display: none; }

.sgt-learn-tabs .nav-link {
    white-space: nowrap;          /* a label must not break across two lines */
    font-size: var(--step--1);
}

@media (min-width: 768px) {
    /* Once they fit, centre them. Left-aligned tabs on a wide screen look like
     * the row has been cut off. */
    .sgt-learn-tabs { justify-content: center; }
}

/* The education module's drawings are inline SVG with no width of their own,
 * so they are sized here rather than inheriting whatever the viewBox implies. */
.sgt-learn-art svg,
.sgt-learn-art img {
    width: 100%;
    height: auto;
    max-height: 90px;
    display: block;
    margin: 0 auto 0.5rem;
}

.sgt-learn-art--sm svg,
.sgt-learn-art--sm img {
    max-height: 44px;
    width: 44px;
    margin: 0;
}

.sgt-learn-card {
    padding: 1rem;
}
