A calm place to write long-form, and publish it to the open social web. skypress.blog/
0

Configure Feed

Select the types of activity you want to include in your feed.

Remove the home-page sign-in island; the hero is just "Start writing"

Drop the HandleStart handle-input CTA and its "Already have an account?" label
from the landing hero (and the now-dead handlestart styles). The home page's
only action is "Start writing → /write"; signing in happens via Publish on
/write, consistent with the rest of the flow. Retire the obsolete
_index.handlestart.test.ts CSS guard and update the landing-content guard.

+12 -154
+5 -6
src/lib/landing/landing-content.test.ts
··· 50 50 describe( 'landing hero redesign', () => { 51 51 const index = read( '../../pages/index.astro' ); 52 52 53 - it( 'still mounts the HandleStart island (now the secondary, returning-user sign-in path)', () => { 54 - expect( index ).toMatch( /import HandleStart from '\.\.\/components\/HandleStart'/ ); 55 - expect( index ).toMatch( /<HandleStart\s+client:only="react"\s*\/>/ ); 53 + it( 'leads with a single "Start writing" CTA to /write and no on-page sign-in', () => { 54 + // Writing-first: the hero's only action starts a draft on /write. The handle sign-in 55 + // island was removed — signing in happens via Publish on /write, not from the home page. 56 + expect( index ).toMatch( /href="\/write"[^>]*>\s*Start writing/ ); 57 + expect( index ).not.toMatch( /HandleStart/ ); 56 58 } ); 57 59 58 60 it( 'drops the old sample / lexicon / studio hero buttons', () => { 59 - // The single "Start writing → /write" primary CTA is the NEW writing-first design 60 - // (it leads the hero above HandleStart) — distinct from the old multi-button cluster, 61 - // whose sample/lexicon/studio links stay gone. 62 61 expect( index ).not.toMatch( /Read a sample/ ); 63 62 expect( index ).not.toMatch( /See the lexicon/ ); 64 63 expect( index ).not.toMatch( /href="\/editor">Studio/ );
-51
src/pages/_index.handlestart.test.ts
··· 1 - /** 2 - * Regression guard for the landing-page handle CTA overflowing on narrow viewports. 3 - * 4 - * `.handlestart__row` is a flexbox row holding the input field (`flex: 1`) and the "Start" 5 - * button. A flex item's default `min-width: auto` refuses to shrink below its content's 6 - * intrinsic width — and the `<input>` carries a sizeable intrinsic floor. Without an explicit 7 - * `min-width: 0` on the field (and the input nested inside it), the field can't shrink on a 8 - * phone-width screen, so the row grows past the container and clips the "Start" button off the 9 - * right edge (reported 2026-06-09, reproduced at <=320px CSS px). 10 - * 11 - * Rendering the page through astro/container isn't viable here (the runner is pinned to jsdom 12 - * for the WordPress block suites), so — as with _index.phase.test.ts — these asserts pin the 13 - * fix at the source level. 14 - */ 15 - import { readFileSync } from 'node:fs'; 16 - import { fileURLToPath } from 'node:url'; 17 - import { describe, expect, it } from 'vitest'; 18 - 19 - const read = ( rel: string ) => 20 - readFileSync( fileURLToPath( new URL( rel, import.meta.url ) ), 'utf8' ); 21 - 22 - /** Pull the body of a CSS rule (between its `{` and the matching `}`) for the given selector. */ 23 - const ruleBody = ( css: string, selector: string ): string => { 24 - const start = css.indexOf( selector ); 25 - if ( start === -1 ) { 26 - return ''; 27 - } 28 - const open = css.indexOf( '{', start ); 29 - const close = css.indexOf( '}', open ); 30 - return css.slice( open + 1, close ); 31 - }; 32 - 33 - describe( 'landing page handle CTA shrinks on narrow viewports', () => { 34 - const style = read( './index.astro' ).match( /<style>([\s\S]*?)<\/style>/ )?.[ 1 ] ?? ''; 35 - 36 - it( 'lets the field shrink below the input intrinsic width (min-width: 0)', () => { 37 - const body = ruleBody( style, '.handlestart__field)' ); 38 - expect( body, 'expected a .handlestart__field rule in the landing styles' ).not.toBe( '' ); 39 - expect( body, '.handlestart__field must set min-width: 0 so it can shrink in the flex row' ).toMatch( 40 - /min-width:\s*0\b/ 41 - ); 42 - } ); 43 - 44 - it( 'lets the input itself shrink inside the field (min-width: 0)', () => { 45 - const body = ruleBody( style, '.handlestart__input)' ); 46 - expect( body, 'expected a .handlestart__input rule in the landing styles' ).not.toBe( '' ); 47 - expect( body, '.handlestart__input must set min-width: 0 so the field can collapse around it' ).toMatch( 48 - /min-width:\s*0\b/ 49 - ); 50 - } ); 51 - } );
+3 -2
src/pages/_index.write-cta.test.ts
··· 27 27 expect( src ).toMatch( /href="\/write"[^>]*>\s*Start writing/ ); 28 28 } ); 29 29 30 - it( 'keeps the handle sign-in as the secondary, returning-user path', () => { 31 - expect( src ).toContain( '<HandleStart' ); 30 + it( 'offers no sign-in island on the home page — signing in happens via Publish on /write', () => { 31 + expect( src ).not.toContain( '<HandleStart' ); 32 + expect( src ).not.toMatch( /import HandleStart/ ); 32 33 } ); 33 34 } );
+4 -95
src/pages/index.astro
··· 3 3 import Logo from '../components/Logo.astro'; 4 4 import Footer from '../components/Footer.astro'; 5 5 import AccountMenu from '../components/AccountMenu.tsx'; 6 - import HandleStart from '../components/HandleStart'; 7 6 import { PHASES, DEFAULT_PHASE } from '../lib/landing/time-of-day'; 8 7 9 8 const fallback = PHASES[ DEFAULT_PHASE ]; ··· 60 59 <p class="hero__lede" id="lede">{fallback.lede}</p> 61 60 <div class="hero__cta"> 62 61 <a class="btn btn--primary hero__start" href="/write">Start writing &rarr;</a> 63 - <p class="hero__signin-label">Already have an account?</p> 64 - <HandleStart client:only="react" /> 65 62 </div> 66 63 <p class="hero__free">Free &amp; open-source. Your words live in your account, not ours.</p> 67 64 </main> ··· 446 443 color: var(--ink-soft); 447 444 } 448 445 449 - /* ===== Primary CTA + handle input island (live on the sky) ===== */ 446 + /* ===== Primary CTA (lives on the sky) ===== */ 450 447 .hero__cta { width: 100%; max-width: 26rem; margin: 2rem auto 0; } 451 - /* Primary "Start writing" action — the writing-first front door. Larger than the default 452 - .btn and lifted off the sky with a soft shadow so it reads at every phase. */ 448 + /* Primary "Start writing" action — the writing-first front door, and the home page's only 449 + CTA (sign-in happens via Publish on /write). Larger than the default .btn and lifted off 450 + the sky with a soft shadow so it reads at every phase. */ 453 451 .hero__start { 454 452 font-size: 1.05rem; 455 453 padding: 0.8rem 1.6rem; 456 454 box-shadow: 0 6px 22px rgba(20, 10, 4, 0.28); 457 455 } 458 - /* Demotes the handle sign-in below the primary CTA: it's the returning-user path now. */ 459 - .hero__signin-label { 460 - color: var(--sky-soft); 461 - font-size: 0.85rem; 462 - margin: 1.5rem 0 0.75rem; 463 - text-shadow: var(--sky-shadow); 464 - } 465 456 .hero__free { 466 457 color: var(--sky-soft); 467 458 font-size: 0.9rem; 468 459 margin-top: 1rem; 469 460 text-shadow: var(--sky-shadow); 470 461 } 471 - .hero :global(.handlestart__row) { display: flex; gap: 0.5rem; } 472 - .hero :global(.handlestart__field) { 473 - flex: 1; 474 - /* Allow the field to shrink below the input's intrinsic width so the row never 475 - overflows and clips the Start button on narrow viewports. */ 476 - min-width: 0; 477 - display: flex; 478 - align-items: center; 479 - gap: 0.15rem; 480 - background: var(--sky-chip); 481 - border: 1px solid var(--sky-line); 482 - border-radius: var(--radius-sm); 483 - padding: 0.6rem 0.75rem; 484 - backdrop-filter: blur(8px); 485 - -webkit-backdrop-filter: blur(8px); 486 - } 487 - .hero :global(.handlestart__at) { color: var(--sky-soft); } 488 - .hero :global(.handlestart__input) { 489 - flex: 1; 490 - /* Pair with the field's min-width: 0 — let the input collapse so the field can too. */ 491 - min-width: 0; 492 - border: none; 493 - background: transparent; 494 - color: var(--sky-ink); 495 - font: inherit; 496 - outline: none; 497 - } 498 - .hero :global(.handlestart__input::placeholder) { color: var(--sky-soft); opacity: 0.7; } 499 - .hero :global(.handlestart__spinner) { 500 - width: 14px; height: 14px; flex: none; 501 - border: 2px solid var(--sky-line); 502 - border-top-color: var(--sky-ink); 503 - border-radius: 50%; 504 - animation: handlestart-spin 0.7s linear infinite; 505 - } 506 - .hero :global(.handlestart__go) { 507 - border: none; 508 - border-radius: var(--radius-sm); 509 - padding: 0.6rem 1rem; 510 - font: inherit; 511 - font-weight: 650; 512 - white-space: nowrap; 513 - color: #fff; 514 - background: var(--btn-primary); 515 - cursor: pointer; 516 - transition: background 0.12s ease, box-shadow 0.12s ease; 517 - } 518 - .hero :global(.handlestart__go:hover) { background: var(--btn-primary-hover); } 519 - .hero :global(.handlestart__card) { 520 - display: flex; 521 - align-items: center; 522 - gap: 0.55rem; 523 - margin-top: 0.6rem; 524 - padding: 0.45rem 0.6rem; 525 - background: var(--sky-chip); 526 - border: 1px solid var(--sky-line); 527 - border-radius: var(--radius-sm); 528 - backdrop-filter: blur(8px); 529 - -webkit-backdrop-filter: blur(8px); 530 - text-align: left; 531 - } 532 - .hero :global(.handlestart__avatar) { 533 - width: 36px; height: 36px; border-radius: 50%; object-fit: cover; flex: none; 534 - } 535 - .hero :global(.handlestart__avatar--fallback) { 536 - display: inline-flex; align-items: center; justify-content: center; 537 - background: var(--sun-tint); color: var(--sun); font-weight: 700; 538 - } 539 - .hero :global(.handlestart__who) { display: flex; flex-direction: column; line-height: 1.15; } 540 - .hero :global(.handlestart__name) { font-weight: 680; font-size: 0.9rem; color: var(--sky-ink); } 541 - .hero :global(.handlestart__handle) { font-size: 0.74rem; color: var(--sky-soft); } 542 - .hero :global(.handlestart__check) { margin-left: auto; color: #6fce92; font-weight: 800; } 543 - .hero :global(.handlestart__hint) { 544 - color: var(--sky-soft); 545 - font-size: 0.82rem; 546 - margin: 0.6rem 0 0; 547 - text-shadow: var(--sky-shadow); 548 - } 549 - .hero :global(.handlestart__hint--error) { color: #ffd0c4; } 550 - @keyframes handlestart-spin { to { transform: rotate(360deg); } } 551 - 552 462 @media (prefers-reduced-motion: reduce) { 553 463 .shootingstar { animation: none; opacity: 0; } 554 464 .sky .stars, .sky .bloom, .sky .halo { transition: none; } 555 - .hero :global(.handlestart__spinner) { animation: none; } 556 465 .masthead :global(.account-menu__dropdown) { 557 466 animation: none; 558 467 }