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:
parent
ed9a050d64
commit
f03d341314
|
|
@ -16,20 +16,20 @@ const value = parseInt(process.argv[2], 10)
|
||||||
const columns = parseInt(process.argv[3], 10)
|
const columns = parseInt(process.argv[3], 10)
|
||||||
|
|
||||||
if (isNaN(value) || isNaN(columns)) {
|
if (isNaN(value) || isNaN(columns)) {
|
||||||
console.error('Usage: npx tsx scripts/generateCalendarAbacus.tsx <value> <columns>')
|
console.error('Usage: npx tsx scripts/generateCalendarAbacus.tsx <value> <columns>')
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use exact same pattern as generateDayIcon - inline customStyles
|
// Use exact same pattern as generateDayIcon - inline customStyles
|
||||||
const abacusMarkup = renderToStaticMarkup(
|
const abacusMarkup = renderToStaticMarkup(
|
||||||
<AbacusReact
|
<AbacusReact
|
||||||
value={value}
|
value={value}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
scaleFactor={1}
|
scaleFactor={1}
|
||||||
animated={false}
|
animated={false}
|
||||||
interactive={false}
|
interactive={false}
|
||||||
showNumbers={false}
|
showNumbers={false}
|
||||||
/>,
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
process.stdout.write(abacusMarkup)
|
process.stdout.write(abacusMarkup)
|
||||||
|
|
|
||||||
|
|
@ -33,30 +33,24 @@ export async function POST(request: NextRequest) {
|
||||||
// Generate SVGs using script (avoids Next.js react-dom/server restriction)
|
// Generate SVGs using script (avoids Next.js react-dom/server restriction)
|
||||||
const daysInMonth = getDaysInMonth(year, month)
|
const daysInMonth = getDaysInMonth(year, month)
|
||||||
const maxDay = format === 'daily' ? daysInMonth : 31 // For monthly, pre-generate all
|
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
|
// Generate day SVGs (1 to maxDay)
|
||||||
const scriptPath = join(process.cwd(), 'scripts', 'generateCalendarSVGs.tsx')
|
for (let day = 1; day <= maxDay; day++) {
|
||||||
const customStylesJson = JSON.stringify(customStyles)
|
const svg = execSync(`npx tsx "${scriptPath}" ${day} 2`, {
|
||||||
const svgsJson = execSync(`npx tsx "${scriptPath}" ${maxDay} ${year} '${customStylesJson}'`, {
|
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',
|
encoding: 'utf-8',
|
||||||
cwd: process.cwd(),
|
cwd: process.cwd(),
|
||||||
})
|
})
|
||||||
|
writeFileSync(join(tempDir, 'year.svg'), yearSvg)
|
||||||
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)
|
|
||||||
|
|
||||||
// Generate Typst document
|
// Generate Typst document
|
||||||
const typstContent =
|
const typstContent =
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { css } from '../../../../styled-system/css'
|
import { css } from '../../../../styled-system/css'
|
||||||
import { useAbacusConfig } from '@soroban/abacus-react'
|
import { useAbacusConfig } from '@soroban/abacus-react'
|
||||||
|
import { PageWithNav } from '@/components/PageWithNav'
|
||||||
import { CalendarConfigPanel } from './components/CalendarConfigPanel'
|
import { CalendarConfigPanel } from './components/CalendarConfigPanel'
|
||||||
import { CalendarPreview } from './components/CalendarPreview'
|
import { CalendarPreview } from './components/CalendarPreview'
|
||||||
|
|
||||||
|
|
@ -54,75 +55,77 @@ export default function CalendarCreatorPage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<PageWithNav navTitle="Create" navEmoji="📅">
|
||||||
data-component="calendar-creator"
|
|
||||||
className={css({
|
|
||||||
minHeight: '100vh',
|
|
||||||
bg: 'gray.900',
|
|
||||||
color: 'white',
|
|
||||||
padding: '2rem',
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
|
data-component="calendar-creator"
|
||||||
className={css({
|
className={css({
|
||||||
maxWidth: '1400px',
|
minHeight: '100vh',
|
||||||
margin: '0 auto',
|
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
|
<div
|
||||||
className={css({
|
className={css({
|
||||||
display: 'grid',
|
maxWidth: '1400px',
|
||||||
gridTemplateColumns: { base: '1fr', lg: '350px 1fr' },
|
margin: '0 auto',
|
||||||
gap: '2rem',
|
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{/* Configuration Panel */}
|
{/* Header */}
|
||||||
<CalendarConfigPanel
|
<header
|
||||||
month={month}
|
data-section="page-header"
|
||||||
year={year}
|
className={css({
|
||||||
format={format}
|
textAlign: 'center',
|
||||||
paperSize={paperSize}
|
marginBottom: '3rem',
|
||||||
isGenerating={isGenerating}
|
})}
|
||||||
onMonthChange={setMonth}
|
>
|
||||||
onYearChange={setYear}
|
<h1
|
||||||
onFormatChange={setFormat}
|
className={css({
|
||||||
onPaperSizeChange={setPaperSize}
|
fontSize: '2.5rem',
|
||||||
onGenerate={handleGenerate}
|
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 */}
|
{/* Main Content */}
|
||||||
<CalendarPreview month={month} year={year} format={format} />
|
<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>
|
</div>
|
||||||
</div>
|
</PageWithNav>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ export default function CreateHubPage() {
|
||||||
<div
|
<div
|
||||||
className={css({
|
className={css({
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
gridTemplateColumns: { base: '1fr', md: '1fr 1fr' },
|
gridTemplateColumns: { base: '1fr', md: '1fr 1fr', lg: '1fr 1fr 1fr' },
|
||||||
gap: 8,
|
gap: 8,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
|
@ -499,6 +499,203 @@ export default function CreateHubPage() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -166,7 +166,8 @@ export function MyAbacus() {
|
||||||
isHeroMode
|
isHeroMode
|
||||||
? {
|
? {
|
||||||
// Hero mode: position accounts for scroll to flow with page (subtract scroll to move up with content)
|
// 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
|
: undefined
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue