fix: adjust hero abacus position to avoid covering subtitle

Move hero abacus down from 50vh to 60vh to prevent it from overlapping
the subtitle text ("master the ancient art..." etc.) on the home page.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock 2025-11-03 13:20:53 -06:00
parent ed9a050d64
commit f03d341314
5 changed files with 286 additions and 91 deletions

View File

@ -16,20 +16,20 @@ const value = parseInt(process.argv[2], 10)
const columns = parseInt(process.argv[3], 10)
if (isNaN(value) || isNaN(columns)) {
console.error('Usage: npx tsx scripts/generateCalendarAbacus.tsx <value> <columns>')
process.exit(1)
console.error('Usage: npx tsx scripts/generateCalendarAbacus.tsx <value> <columns>')
process.exit(1)
}
// Use exact same pattern as generateDayIcon - inline customStyles
const abacusMarkup = renderToStaticMarkup(
<AbacusReact
value={value}
columns={columns}
scaleFactor={1}
animated={false}
interactive={false}
showNumbers={false}
/>,
<AbacusReact
value={value}
columns={columns}
scaleFactor={1}
animated={false}
interactive={false}
showNumbers={false}
/>
)
process.stdout.write(abacusMarkup)

View File

@ -33,30 +33,24 @@ export async function POST(request: NextRequest) {
// Generate SVGs using script (avoids Next.js react-dom/server restriction)
const daysInMonth = getDaysInMonth(year, month)
const maxDay = format === 'daily' ? daysInMonth : 31 // For monthly, pre-generate all
const customStyles = abacusConfig?.customStyles || {}
const scriptPath = join(process.cwd(), 'scripts', 'generateCalendarAbacus.tsx')
// Call script to generate all SVGs
const scriptPath = join(process.cwd(), 'scripts', 'generateCalendarSVGs.tsx')
const customStylesJson = JSON.stringify(customStyles)
const svgsJson = execSync(`npx tsx "${scriptPath}" ${maxDay} ${year} '${customStylesJson}'`, {
// Generate day SVGs (1 to maxDay)
for (let day = 1; day <= maxDay; day++) {
const svg = execSync(`npx tsx "${scriptPath}" ${day} 2`, {
encoding: 'utf-8',
cwd: process.cwd(),
})
writeFileSync(join(tempDir, `day-${day}.svg`), svg)
}
// Generate year SVG
const yearColumns = Math.max(1, Math.ceil(Math.log10(year + 1)))
const yearSvg = execSync(`npx tsx "${scriptPath}" ${year} ${yearColumns}`, {
encoding: 'utf-8',
cwd: process.cwd(),
})
interface CalendarSVGs {
days: Record<string, string>
year: string
}
const svgs: CalendarSVGs = JSON.parse(svgsJson)
// Write day SVGs to temp directory
for (const [key, svg] of Object.entries(svgs.days)) {
writeFileSync(join(tempDir, `${key}.svg`), svg)
}
// Write year SVG
writeFileSync(join(tempDir, 'year.svg'), svgs.year)
writeFileSync(join(tempDir, 'year.svg'), yearSvg)
// Generate Typst document
const typstContent =

View File

@ -3,6 +3,7 @@
import { useState } from 'react'
import { css } from '../../../../styled-system/css'
import { useAbacusConfig } from '@soroban/abacus-react'
import { PageWithNav } from '@/components/PageWithNav'
import { CalendarConfigPanel } from './components/CalendarConfigPanel'
import { CalendarPreview } from './components/CalendarPreview'
@ -54,75 +55,77 @@ export default function CalendarCreatorPage() {
}
return (
<div
data-component="calendar-creator"
className={css({
minHeight: '100vh',
bg: 'gray.900',
color: 'white',
padding: '2rem',
})}
>
<PageWithNav navTitle="Create" navEmoji="📅">
<div
data-component="calendar-creator"
className={css({
maxWidth: '1400px',
margin: '0 auto',
minHeight: '100vh',
bg: 'gray.900',
color: 'white',
padding: '2rem',
})}
>
{/* Header */}
<header
data-section="page-header"
className={css({
textAlign: 'center',
marginBottom: '3rem',
})}
>
<h1
className={css({
fontSize: '2.5rem',
fontWeight: 'bold',
marginBottom: '0.5rem',
color: 'yellow.400',
})}
>
Create Abacus Calendar
</h1>
<p
className={css({
fontSize: '1.125rem',
color: 'gray.300',
})}
>
Generate printable calendars with abacus date numbers
</p>
</header>
{/* Main Content */}
<div
className={css({
display: 'grid',
gridTemplateColumns: { base: '1fr', lg: '350px 1fr' },
gap: '2rem',
maxWidth: '1400px',
margin: '0 auto',
})}
>
{/* Configuration Panel */}
<CalendarConfigPanel
month={month}
year={year}
format={format}
paperSize={paperSize}
isGenerating={isGenerating}
onMonthChange={setMonth}
onYearChange={setYear}
onFormatChange={setFormat}
onPaperSizeChange={setPaperSize}
onGenerate={handleGenerate}
/>
{/* Header */}
<header
data-section="page-header"
className={css({
textAlign: 'center',
marginBottom: '3rem',
})}
>
<h1
className={css({
fontSize: '2.5rem',
fontWeight: 'bold',
marginBottom: '0.5rem',
color: 'yellow.400',
})}
>
Create Abacus Calendar
</h1>
<p
className={css({
fontSize: '1.125rem',
color: 'gray.300',
})}
>
Generate printable calendars with abacus date numbers
</p>
</header>
{/* Preview */}
<CalendarPreview month={month} year={year} format={format} />
{/* Main Content */}
<div
className={css({
display: 'grid',
gridTemplateColumns: { base: '1fr', lg: '350px 1fr' },
gap: '2rem',
})}
>
{/* Configuration Panel */}
<CalendarConfigPanel
month={month}
year={year}
format={format}
paperSize={paperSize}
isGenerating={isGenerating}
onMonthChange={setMonth}
onYearChange={setYear}
onFormatChange={setFormat}
onPaperSizeChange={setPaperSize}
onGenerate={handleGenerate}
/>
{/* Preview */}
<CalendarPreview month={month} year={year} format={format} />
</div>
</div>
</div>
</div>
</PageWithNav>
)
}

View File

@ -102,7 +102,7 @@ export default function CreateHubPage() {
<div
className={css({
display: 'grid',
gridTemplateColumns: { base: '1fr', md: '1fr 1fr' },
gridTemplateColumns: { base: '1fr', md: '1fr 1fr', lg: '1fr 1fr 1fr' },
gap: 8,
})}
>
@ -499,6 +499,203 @@ export default function CreateHubPage() {
</div>
</div>
</Link>
{/* Calendar Creator */}
<Link href="/create/calendar">
<div
data-element="calendar-card"
className={css({
bg: 'white',
borderRadius: '3xl',
p: 8,
boxShadow: '0 20px 60px rgba(0,0,0,0.25)',
cursor: 'pointer',
transition: 'all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275)',
position: 'relative',
overflow: 'hidden',
_hover: {
transform: 'translateY(-12px) scale(1.02)',
boxShadow: '0 30px 80px rgba(0,0,0,0.35)',
},
_before: {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '6px',
background: 'linear-gradient(90deg, #fbbf24 0%, #f59e0b 100%)',
},
})}
>
{/* Icon with gradient background */}
<div
className={css({
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '4xl',
mb: 5,
width: '80px',
height: '80px',
borderRadius: '2xl',
background: 'linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%)',
boxShadow: '0 8px 24px rgba(251, 191, 36, 0.4)',
})}
>
📅
</div>
{/* Title */}
<h2
className={css({
fontSize: '2xl',
fontWeight: 'extrabold',
mb: 3,
color: 'gray.900',
letterSpacing: 'tight',
})}
>
Abacus Calendar
</h2>
{/* Description */}
<p
className={css({
fontSize: 'md',
color: 'gray.600',
mb: 5,
lineHeight: '1.7',
})}
>
Generate printable calendars where every date is shown as an abacus. Perfect for
teaching number representation.
</p>
{/* Features */}
<ul
className={css({
listStyle: 'none',
display: 'flex',
flexDirection: 'column',
gap: 3,
})}
>
<li
className={css({
display: 'flex',
alignItems: 'center',
gap: 3,
fontSize: 'sm',
color: 'gray.700',
})}
>
<span
className={css({
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
width: '20px',
height: '20px',
borderRadius: 'full',
bg: 'yellow.100',
color: 'yellow.600',
fontSize: 'xs',
fontWeight: 'bold',
})}
>
</span>
Monthly or daily formats
</li>
<li
className={css({
display: 'flex',
alignItems: 'center',
gap: 3,
fontSize: 'sm',
color: 'gray.700',
})}
>
<span
className={css({
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
width: '20px',
height: '20px',
borderRadius: 'full',
bg: 'yellow.100',
color: 'yellow.600',
fontSize: 'xs',
fontWeight: 'bold',
})}
>
</span>
Multiple paper sizes
</li>
<li
className={css({
display: 'flex',
alignItems: 'center',
gap: 3,
fontSize: 'sm',
color: 'gray.700',
})}
>
<span
className={css({
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
width: '20px',
height: '20px',
borderRadius: 'full',
bg: 'yellow.100',
color: 'yellow.600',
fontSize: 'xs',
fontWeight: 'bold',
})}
>
</span>
Uses your abacus styling
</li>
</ul>
{/* CTA Button */}
<div
className={css({
mt: 7,
})}
>
<div
className={css({
display: 'inline-flex',
alignItems: 'center',
gap: 2,
px: 6,
py: 3,
borderRadius: 'xl',
background: 'linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%)',
color: 'white',
fontWeight: 'bold',
fontSize: 'md',
boxShadow: '0 4px 15px rgba(251, 191, 36, 0.4)',
transition: 'all 0.3s',
_hover: {
boxShadow: '0 6px 20px rgba(251, 191, 36, 0.5)',
transform: 'translateX(4px)',
},
})}
>
<span>Create Calendar</span>
<span className={css({ fontSize: 'lg' })}></span>
</div>
</div>
</div>
</Link>
</div>
</div>
</div>

View File

@ -166,7 +166,8 @@ export function MyAbacus() {
isHeroMode
? {
// Hero mode: position accounts for scroll to flow with page (subtract scroll to move up with content)
top: `calc(50vh - ${scrollY}px)`,
// Positioned lower (60vh instead of 50vh) to avoid covering subtitle
top: `calc(60vh - ${scrollY}px)`,
}
: undefined
}