/* =====================================================================
   jackieywang.com
   ===================================================================== */

:root {
    /* Purples, dark to light */
    --void:      #0B0512;
    --ink:       #12081F;
    --plum:      #1B0F2E;
    --plum-lift: #241539;

    --violet:    #8B5CF6;
    --orchid:    #C084FC;
    --lilac:     #E4D2FF;
    --blush:     #F5A9E0;

    --text:      #F3ECFF;
    --muted:     #A38FC4;
    --hairline:  rgba(196, 164, 255, .16);

    /* Parisienne comes from the Google Fonts CDN (see the <link> in
       index.php, loaded with display=swap so text is never invisible).
       The rest are real installed scripts — Snell/Apple Chancery on macOS,
       Segoe Script on Windows, Brush Script as the common fallback — so a
       blocked CDN degrades to a script face instead of generic `cursive`,
       which resolves to Comic Sans on a lot of Windows machines. */
    --font-display: 'Parisienne', 'Snell Roundhand', 'Apple Chancery',
                    'Segoe Script', 'Brush Script MT', cursive;
    --font-ui: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;

    --page:  min(1080px, 100% - 2.5rem);
    --ease:  cubic-bezier(.22, .61, .36, 1);
}

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

html {
    scroll-behavior: smooth;
    -webkit-text-size-adjust: 100%;
    /* The tulip watermarks deliberately bleed past the edge. `clip` cuts
       them off without making the element a scroll container — which is
       why it is used instead of `hidden`, since `hidden` here would also
       break the sticky header. `hidden` stays first as the fallback for
       browsers without `clip`. */
    overflow-x: hidden;
    overflow-x: clip;
}

body {
    margin: 0;
    min-height: 100svh;
    background: var(--void);
    color: var(--text);
    font-family: var(--font-ui);
    font-size: 1rem;
    line-height: 1.65;
    letter-spacing: .002em;
    -webkit-font-smoothing: antialiased;
    overflow-x: hidden;
    overflow-x: clip;
}

img, svg { max-width: 100%; }

::selection {
    background: rgba(192, 132, 252, .32);
    color: #fff;
}

:focus-visible {
    outline: 2px solid var(--orchid);
    outline-offset: 3px;
    border-radius: 4px;
}

.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: absolute;
    left: 50%;
    top: -4rem;
    transform: translateX(-50%);
    z-index: 100;
    padding: .7rem 1.2rem;
    background: var(--plum-lift);
    color: var(--text);
    border-radius: 0 0 12px 12px;
    text-decoration: none;
    transition: top .2s var(--ease);
}
.skip-link:focus { top: 0; }

/* ---------------------------------------------------------------------
   Backdrop: three slow orbs and a film of grain
   --------------------------------------------------------------------- */

.aurora {
    position: fixed;
    inset: 0;
    z-index: -2;
    overflow: hidden;
    background:
        radial-gradient(120% 90% at 50% 0%, #1D0F33 0%, var(--ink) 45%, var(--void) 100%);
}

.orb {
    position: absolute;
    display: block;
    border-radius: 50%;
    filter: blur(90px);
    opacity: .55;
    will-change: transform;
}

.orb--one {
    width: 46vw; height: 46vw;
    min-width: 320px; min-height: 320px;
    top: -12vh; left: -8vw;
    background: radial-gradient(circle at 30% 30%, #7C3AED, transparent 68%);
    animation: drift-one 26s var(--ease) infinite alternate;
}

.orb--two {
    width: 40vw; height: 40vw;
    min-width: 280px; min-height: 280px;
    top: 28vh; right: -10vw;
    background: radial-gradient(circle at 60% 40%, #C084FC, transparent 66%);
    opacity: .42;
    animation: drift-two 32s var(--ease) infinite alternate;
}

.orb--three {
    width: 34vw; height: 34vw;
    min-width: 240px; min-height: 240px;
    bottom: -14vh; left: 22vw;
    background: radial-gradient(circle at 50% 50%, #F5A9E0, transparent 64%);
    opacity: .26;
    animation: drift-three 38s var(--ease) infinite alternate;
}

@keyframes drift-one {
    from { transform: translate3d(0, 0, 0) scale(1); }
    to   { transform: translate3d(6vw, 8vh, 0) scale(1.14); }
}
@keyframes drift-two {
    from { transform: translate3d(0, 0, 0) scale(1.06); }
    to   { transform: translate3d(-7vw, -6vh, 0) scale(.92); }
}
@keyframes drift-three {
    from { transform: translate3d(0, 0, 0) scale(1); }
    to   { transform: translate3d(9vw, -5vh, 0) scale(1.2); }
}

/* Fine noise, so the big gradients don't band on cheap panels. */
.grain {
    position: fixed;
    inset: -50%;
    z-index: -1;
    pointer-events: none;
    opacity: .035;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---------------------------------------------------------------------
   Top bar
   --------------------------------------------------------------------- */

.topbar {
    position: sticky;
    top: 0;
    z-index: 20;
    display: flex;
    align-items: center;
    width: var(--page);
    margin-inline: auto;
    padding: 1.15rem 0;
}

.topnav {
    display: flex;
    align-items: center;
    gap: 1.6rem;
}

/* The tulip marks the menu and doubles as the back-to-top control. */
.topnav__home {
    display: grid;
    place-items: center;
    color: var(--muted);
    line-height: 0;
    transition: transform .35s var(--ease), color .35s var(--ease);
}
.topnav__home:hover {
    transform: translateY(-2px);
    color: var(--orchid);
}
.topnav__home::after { content: none; }

/* The logo artwork is 610x869, so it is sized by height and left to work
   out its own width. */
.tulip {
    display: block;
    height: 30px;
    width: auto;
}

/* ---------------------------------------------------------------------
   Tulip watermarks — decoration only, never interactive.

   These sit at z-index 0 and the real content is lifted to 1, rather
   than pushing the watermark to a negative z-index: negative children
   escape to the root stacking context and end up tangled with the
   fixed aurora and grain layers.
   --------------------------------------------------------------------- */

.watermark {
    position: absolute;
    z-index: 0;
    line-height: 0;
    pointer-events: none;
    user-select: none;
}

.watermark img {
    display: block;
    width: 100%;
    height: auto;
}

/* Hero: bloom clears the name, only the stem runs behind it.
   In the artwork the bloom occupies the top ~49% and the foliage the rest,
   so the -58% vertical offset (rather than -50%) is what lifts the
   flower head clear of the text instead of sitting on top of it.
   Widths are ~1/1.42 of what a square mark would need, since this
   artwork is that much taller than it is wide. */
.watermark--hero {
    top: 50%;
    left: 50%;
    width: min(26rem, 46vh);
    translate: -50% -58%;
    opacity: .06;
}

/* Contact: centered on the section so the bloom clears the form card and
   only the stem passes behind it. Offset right and tilted so it reads as
   a second look at the motif rather than a repeat of the hero.
   The card is semi-opaque, so anything sitting fully behind it vanishes —
   the flower head has to sit above the card's top edge to be seen. */
.watermark--contact {
    top: -2.5rem;
    left: 58%;
    translate: -50% 0;
    width: min(21rem, 52vw);
    rotate: 9deg;
    opacity: .07;
}

/* Keep every piece of real content above the decoration. */
.hero__inner,
.section--contact .section__title,
.section--contact .section__lede,
.section--contact .alert,
.form {
    position: relative;
    z-index: 1;
}

.section--contact { position: relative; }

.topnav a {
    position: relative;
    color: var(--muted);
    font-size: .82rem;
    font-weight: 500;
    letter-spacing: .14em;
    text-transform: uppercase;
    text-decoration: none;
    transition: color .25s var(--ease);
}
.topnav a::after {
    content: '';
    position: absolute;
    left: 0; bottom: -.4rem;
    width: 100%; height: 1px;
    background: var(--orchid);
    transform: scaleX(0);
    transform-origin: right;
    transition: transform .35s var(--ease);
}
.topnav a:hover { color: var(--text); }
.topnav a:hover::after { transform: scaleX(1); transform-origin: left; }

/* ---------------------------------------------------------------------
   Hero
   --------------------------------------------------------------------- */

.hero {
    position: relative;
    display: grid;
    place-items: center;
    min-height: calc(100svh - 5rem);
    width: var(--page);
    margin-inline: auto;
    padding: 3rem 0 6rem;
    text-align: center;
}

.hero__inner {
    animation: rise 1.1s var(--ease) both;
}

/* Sized to fit "Jackie Y. Wang" on one line at any width — the vw term
   is what keeps it from wrapping on a narrow phone. Script faces run
   small for their point size, so this is set larger than a serif would be.
   Letter-spacing stays at zero: tightening a connecting script breaks
   the joins between letters. */
/* Measured: rendered width is 6.82x the font-size in Parisienne. The
   container is 100vw - 2.5rem, so anything above ~14.6vw overflows.
   12vw keeps a margin at every width — and there is deliberately NO
   lower bound, because a clamp() minimum is what pushes the name off
   the side of a 320px phone. It shrinks instead of wrapping. */
.wordmark {
    margin: 0;
    font-family: var(--font-display);
    font-weight: 400;
    font-size: min(11.5vw, 8.75rem);
    line-height: 1.24;
    letter-spacing: 0;
    white-space: nowrap;
    /* Script capitals draw outside their advance box — the J's entry loop
       reaches ~0.2em left of where the text formally starts. Padding in em
       reserves that room at every size, so the flourish can never be
       clipped by the container or by the overflow-x:clip on <html>. It
       also covers the fallback faces, whose J swashes are larger still. */
    padding-inline: .22em;
}

/* Solid color, deliberately NOT a gradient. A gradient here needs
   background-clip:text, which only paints inside this span's box — and
   Parisienne's Y swashes far outside it. Ink beyond the box turns
   transparent and the letter looks sliced. Plain color has no box to
   escape, so the swash always draws in full. Don't reintroduce a
   gradient on this element. */
.wordmark__accent {
    color: var(--orchid);
}

/* The tulip standing in for the Y. Sized in em so it tracks the wordmark
   at every viewport, and nudged down so its stem sits on the baseline
   rather than floating in the middle of the line. */
.wordmark__logo {
    display: inline-block;
    height: 1.18em;
    width: auto;
    vertical-align: -.26em;
    margin-inline: .04em;
}

.hero__rule {
    width: 74px;
    height: 1px;
    margin: 2.2rem auto 0;
    background: linear-gradient(90deg, transparent, var(--orchid), transparent);
}

/* Mirrors the eyebrow above the name, so the two frame it evenly. */
.hero__title {
    margin: 1.5rem 0 0;
    color: var(--lilac);
    font-size: clamp(.76rem, 2.1vw, .88rem);
    font-weight: 500;
    letter-spacing: .3em;
    text-transform: uppercase;
    /* The tracking adds space after the last letter; this re-centers it. */
    text-indent: .3em;
}

@keyframes rise {
    from { opacity: 0; transform: translateY(22px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* ---------------------------------------------------------------------
   Sections
   --------------------------------------------------------------------- */

.section {
    width: var(--page);
    margin-inline: auto;
    padding: clamp(4rem, 11vh, 7.5rem) 0;
}

.section__title {
    margin: 0 0 .6rem;
    font-family: var(--font-display);
    font-weight: 400;
    font-size: clamp(2.3rem, 6vw, 3.6rem);
    line-height: 1.28;
    letter-spacing: 0;
}

.section__lede {
    max-width: 46ch;
    margin: 0 0 2.6rem;
    color: var(--muted);
    font-size: 1.02rem;
}

.section__title + .links { margin-top: 2.6rem; }

/* ---------------------------------------------------------------------
   Link cards
   --------------------------------------------------------------------- */

.links {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(258px, 1fr));
    gap: 1rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

.link-card {
    position: relative;
    display: flex;
    align-items: center;
    gap: 1.05rem;
    padding: 1.3rem 1.4rem;
    overflow: hidden;
    border: 1px solid var(--hairline);
    border-radius: 18px;
    background: linear-gradient(150deg, rgba(255, 255, 255, .05), rgba(255, 255, 255, .014));
    color: var(--text);
    text-decoration: none;
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    transition: transform .35s var(--ease), border-color .35s var(--ease), box-shadow .35s var(--ease);
}

/* Sheen that sweeps across on hover. */
.link-card::before {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(115deg, transparent 38%, rgba(224, 200, 255, .13) 50%, transparent 62%);
    translate: -100% 0;
    transition: translate .7s var(--ease);
}

.link-card:hover,
.link-card:focus-visible {
    transform: translateY(-4px);
    border-color: rgba(192, 132, 252, .5);
    box-shadow: 0 18px 44px -22px rgba(139, 92, 246, .85);
}
.link-card:hover::before { translate: 100% 0; }

.link-card__icon {
    display: grid;
    place-items: center;
    flex: 0 0 auto;
    width: 44px;
    height: 44px;
    border-radius: 13px;
    background: rgba(139, 92, 246, .17);
    color: var(--lilac);
    transition: background .35s var(--ease), color .35s var(--ease);
}
.link-card:hover .link-card__icon {
    background: rgba(192, 132, 252, .3);
    color: #fff;
}
.link-card__icon .icon { width: 21px; height: 21px; }

.link-card__text {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.link-card__label {
    font-weight: 600;
    font-size: 1rem;
    letter-spacing: -.005em;
}

.link-card__handle {
    color: var(--muted);
    font-size: .85rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.link-card__arrow {
    margin-left: auto;
    color: var(--muted);
    opacity: .6;
    transition: transform .35s var(--ease), opacity .35s var(--ease), color .35s var(--ease);
}
.link-card__arrow svg { display: block; width: 18px; height: 18px; }
.link-card:hover .link-card__arrow {
    transform: translate(3px, -3px);
    opacity: 1;
    color: var(--orchid);
}

/* ---------------------------------------------------------------------
   Contact form
   --------------------------------------------------------------------- */

.section--contact { max-width: 620px; }

.form {
    display: grid;
    gap: 1.35rem;
    padding: clamp(1.5rem, 4vw, 2.4rem);
    border: 1px solid var(--hairline);
    border-radius: 22px;
    background: linear-gradient(155deg, rgba(255, 255, 255, .055), rgba(255, 255, 255, .012));
    backdrop-filter: blur(16px);
    -webkit-backdrop-filter: blur(16px);
    box-shadow: 0 30px 70px -50px rgba(0, 0, 0, .95);
}

.field { display: grid; gap: .5rem; }

.field label {
    color: var(--lilac);
    font-size: .77rem;
    font-weight: 500;
    letter-spacing: .12em;
    text-transform: uppercase;
}

.field input,
.field textarea {
    width: 100%;
    padding: .82rem .95rem;
    border: 1px solid var(--hairline);
    border-radius: 12px;
    background: rgba(11, 5, 18, .55);
    color: var(--text);
    font-family: inherit;
    font-size: 1rem;
    line-height: 1.55;
    transition: border-color .25s var(--ease), background .25s var(--ease), box-shadow .25s var(--ease);
}

.field textarea { resize: vertical; min-height: 8.5rem; }

.field input::placeholder,
.field textarea::placeholder { color: rgba(163, 143, 196, .65); }

.field input:focus,
.field textarea:focus {
    outline: none;
    border-color: rgba(192, 132, 252, .68);
    background: rgba(11, 5, 18, .78);
    box-shadow: 0 0 0 3px rgba(139, 92, 246, .2);
}

.field--invalid input,
.field--invalid textarea { border-color: #F2789B; }

.field__error {
    margin: 0;
    color: #F9A8C0;
    font-size: .84rem;
    min-height: 0;
}
.field__error:empty { display: none; }

/* Honeypot: off-screen rather than display:none, which some bots skip. */
.hp {
    position: absolute;
    left: -9999px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

/* Captcha */

.captcha {
    display: grid;
    grid-template-columns: auto auto 1fr;
    align-items: center;
    gap: .7rem;
}

.captcha__image {
    display: block;
    width: 168px;
    height: auto;
    border: 1px solid var(--hairline);
    border-radius: 10px;
    background: var(--plum);
}

.captcha__refresh {
    display: grid;
    place-items: center;
    width: 38px;
    height: 38px;
    padding: 0;
    border: 1px solid var(--hairline);
    border-radius: 10px;
    background: rgba(255, 255, 255, .04);
    color: var(--muted);
    cursor: pointer;
    transition: color .25s var(--ease), border-color .25s var(--ease), transform .4s var(--ease);
}
.captcha__refresh:hover {
    color: var(--orchid);
    border-color: rgba(192, 132, 252, .5);
    transform: rotate(-180deg);
}
.captcha__refresh svg { width: 17px; height: 17px; }

.captcha input {
    min-width: 0;
    letter-spacing: .22em;
    text-transform: uppercase;
    font-size: 1.05rem;
}

/* Submit */

.submit {
    position: relative;
    justify-self: start;
    display: inline-flex;
    align-items: center;
    gap: .6rem;
    margin-top: .3rem;
    padding: .92rem 2rem;
    border: 0;
    border-radius: 999px;
    background: linear-gradient(100deg, var(--violet), var(--orchid) 62%, var(--blush));
    color: #17091F;
    font-family: inherit;
    font-size: .95rem;
    font-weight: 600;
    letter-spacing: .01em;
    cursor: pointer;
    box-shadow: 0 12px 32px -16px rgba(192, 132, 252, .9);
    transition: transform .25s var(--ease), box-shadow .25s var(--ease), filter .25s var(--ease);
}
.submit:hover {
    transform: translateY(-2px);
    filter: brightness(1.07);
    box-shadow: 0 18px 40px -16px rgba(192, 132, 252, 1);
}
.submit:active { transform: translateY(0); }

.submit[disabled] {
    cursor: progress;
    opacity: .72;
    transform: none;
}

.submit__spinner {
    display: none;
    width: 15px;
    height: 15px;
    border: 2px solid rgba(23, 9, 31, .35);
    border-top-color: #17091F;
    border-radius: 50%;
    animation: spin .7s linear infinite;
}
.submit.is-sending .submit__spinner { display: block; }

@keyframes spin { to { transform: rotate(360deg); } }

/* Alerts */

.alert {
    margin-bottom: 1.6rem;
    padding: .95rem 1.15rem;
    border: 1px solid;
    border-radius: 14px;
    font-size: .93rem;
}
.alert--success {
    border-color: rgba(134, 239, 172, .38);
    background: rgba(74, 222, 128, .1);
    color: #C9F7DA;
}
.alert--error {
    border-color: rgba(249, 168, 192, .4);
    background: rgba(242, 120, 155, .1);
    color: #FFD5E2;
}

/* ---------------------------------------------------------------------
   Footer
   --------------------------------------------------------------------- */

.footer {
    border-top: 1px solid var(--hairline);
    margin-top: 2rem;
}

.footer__inner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    width: var(--page);
    margin-inline: auto;
    padding: 2rem 0 2.6rem;
    color: var(--muted);
    font-size: .84rem;
}

.footer__links {
    display: flex;
    gap: 1.15rem;
}
.footer__links a {
    color: var(--muted);
    transition: color .25s var(--ease), transform .25s var(--ease);
}
.footer__links a:hover {
    color: var(--orchid);
    transform: translateY(-2px);
}
.footer__links .icon { display: block; width: 18px; height: 18px; }

/* ---------------------------------------------------------------------
   Narrow screens
   --------------------------------------------------------------------- */

@media (max-width: 560px) {
    .topnav { gap: 1.2rem; }
    .captcha {
        grid-template-columns: auto auto;
        row-gap: .8rem;
    }
    .captcha input { grid-column: 1 / -1; }
    .footer__inner { justify-content: center; text-align: center; }
}

/* ---------------------------------------------------------------------
   Reduced motion — kill the drift, keep the layout
   --------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
    *, *::before, *::after {
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .01ms !important;
    }
    .orb { animation: none; }
}
