Tiles in the same row had different widths because grid items default to min-width: auto, which makes them grow to fit their intrinsic content width instead of dividing the row equally. Add min-width: 0, width: 100%, and box-sizing: border-box to bento-tile so all tiles in a row are exactly equal width. Co-Authored-By: Claude <noreply@anthropic.com>
169 lines
5.6 KiB
Plaintext
169 lines
5.6 KiB
Plaintext
---
|
|
/**
|
|
* BentoTile — a single bento grid cell.
|
|
*
|
|
* Props:
|
|
* span: 3 | 4 | 5 | 6 | 7 | 8 | 12 (default 6)
|
|
* rows: 1 | 2 | 3 (default 1)
|
|
* surface: 'white' | 'soft' | 'yellow' | 'purple' | 'purple-soft' | 'teal' | 'mint' | 'dark' | 'coral'
|
|
* minHeight: optional inline min-height CSS value
|
|
* eyebrow: optional small uppercase label above title
|
|
* title: optional H2 title
|
|
* reveal: boolean, animate on scroll into view (default true)
|
|
*
|
|
* Example:
|
|
* <BentoTile span={8} surface="yellow" eyebrow="วิธีทำงาน" title="ไม่ได้ทำงานแบบเดียวกับทุกที่">
|
|
* <p>...content...</p>
|
|
* </BentoTile>
|
|
*/
|
|
|
|
interface Props {
|
|
span?: 3 | 4 | 5 | 6 | 7 | 8 | 12;
|
|
rows?: 1 | 2 | 3;
|
|
surface?: 'white' | 'soft' | 'yellow' | 'purple' | 'purple-soft' | 'teal' | 'mint' | 'dark' | 'coral';
|
|
minHeight?: string;
|
|
eyebrow?: string;
|
|
title?: string;
|
|
reveal?: boolean;
|
|
class?: string;
|
|
}
|
|
|
|
const {
|
|
span = 6,
|
|
rows = 1,
|
|
surface = 'white',
|
|
minHeight,
|
|
eyebrow,
|
|
title,
|
|
reveal = true,
|
|
class: className = '',
|
|
} = Astro.props;
|
|
|
|
const spanClass = `span-${span}`;
|
|
const rowsClass = rows > 1 ? `rows-${rows}` : '';
|
|
const surfaceClass = `surface-${surface}`;
|
|
const revealClass = reveal ? 'reveal' : '';
|
|
---
|
|
|
|
<div
|
|
class:list={['bento-tile', spanClass, rowsClass, surfaceClass, revealClass, className]}
|
|
style={minHeight ? `min-height: ${minHeight};` : undefined}
|
|
>
|
|
{eyebrow && <div class:list={['tile-eyebrow', surface === 'dark' || surface === 'purple' || surface === 'teal' || surface === 'coral' ? 'inv' : '']}>{eyebrow}</div>}
|
|
{title && <h2 class:list={['tile-title', surface === 'dark' || surface === 'purple' || surface === 'teal' || surface === 'coral' ? 'light' : '']}>{title}</h2>}
|
|
<div class="tile-body">
|
|
<slot />
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.bento-tile {
|
|
background: var(--color-white);
|
|
color: var(--color-black);
|
|
border: 1px solid var(--color-gray-200);
|
|
border-radius: var(--radius-xl);
|
|
padding: 32px;
|
|
position: relative;
|
|
overflow: hidden;
|
|
transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.4s ease;
|
|
transform-style: preserve-3d;
|
|
min-height: 380px;
|
|
min-width: 0;
|
|
width: 100%;
|
|
box-sizing: border-box;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.bento-tile:hover {
|
|
transform: translateY(-4px);
|
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.08);
|
|
}
|
|
|
|
/* Spans */
|
|
.span-3 { grid-column: span 3; }
|
|
.span-4 { grid-column: span 4; }
|
|
.span-5 { grid-column: span 5; }
|
|
.span-6 { grid-column: span 6; }
|
|
.span-7 { grid-column: span 7; }
|
|
.span-8 { grid-column: span 8; }
|
|
.span-12 { grid-column: span 12; }
|
|
.rows-2 { grid-row: span 2; }
|
|
.rows-3 { grid-row: span 3; }
|
|
|
|
@media (max-width: 1024px) {
|
|
.span-3, .span-4, .span-5 { grid-column: span 3; }
|
|
.span-6, .span-7, .span-8 { grid-column: span 6; }
|
|
.span-12 { grid-column: span 6; }
|
|
}
|
|
@media (max-width: 640px) {
|
|
[class*="span-"] { grid-column: span 1; }
|
|
.rows-2, .rows-3 { grid-row: span 1; }
|
|
}
|
|
|
|
/* Surface variants */
|
|
.surface-soft { background: var(--color-bg-soft); border-color: var(--color-gray-200); }
|
|
.surface-yellow { background: var(--color-primary); border-color: var(--color-primary); color: var(--color-black); }
|
|
.surface-purple { background: var(--color-purple); border-color: var(--color-purple); color: var(--color-white); }
|
|
.surface-purple-soft { background: var(--color-purple-soft); border-color: var(--color-purple-soft); }
|
|
.surface-teal { background: var(--color-teal); border-color: var(--color-teal); color: var(--color-white); }
|
|
.surface-mint { background: var(--color-mint-soft); border-color: var(--color-mint-soft); }
|
|
.surface-coral { background: var(--color-coral); border-color: var(--color-coral); color: var(--color-white); }
|
|
.surface-dark { background: var(--color-black); border-color: var(--color-black); color: var(--color-white); }
|
|
|
|
/* Typography */
|
|
.tile-eyebrow {
|
|
font-size: 11px;
|
|
font-weight: 800;
|
|
text-transform: uppercase;
|
|
letter-spacing: 2px;
|
|
opacity: 0.7;
|
|
margin-bottom: 12px;
|
|
}
|
|
.tile-eyebrow.inv {
|
|
opacity: 1;
|
|
color: var(--color-primary);
|
|
}
|
|
.tile-title {
|
|
font-family: var(--font-display);
|
|
font-size: clamp(22px, 3vw, 36px);
|
|
font-weight: 900;
|
|
line-height: 1.1;
|
|
margin-bottom: 16px;
|
|
}
|
|
.tile-title.light { color: var(--color-white); }
|
|
.tile-body { font-size: 16px; line-height: 1.7; }
|
|
.tile-body :global(p) { margin-bottom: 12px; }
|
|
.tile-body :global(p:last-child) { margin-bottom: 0; }
|
|
.tile-body :global(ul) { padding-left: 20px; margin-top: 12px; }
|
|
.tile-body :global(li) { margin-bottom: 8px; }
|
|
.tile-body :global(strong) { font-weight: 800; }
|
|
|
|
/* Light text on dark/colored surface */
|
|
.surface-dark .tile-body,
|
|
.surface-purple .tile-body,
|
|
.surface-teal .tile-body,
|
|
.surface-coral .tile-body {
|
|
color: rgba(255, 255, 255, 0.95);
|
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
|
|
}
|
|
.surface-dark .tile-body :global(strong),
|
|
.surface-purple .tile-body :global(strong),
|
|
.surface-teal .tile-body :global(strong),
|
|
.surface-coral .tile-body :global(strong) { color: var(--color-primary); }
|
|
|
|
/* Yellow tile — always black text */
|
|
.surface-yellow .tile-title,
|
|
.surface-yellow .tile-body,
|
|
.surface-yellow .tile-body :global(strong) { color: var(--color-black); }
|
|
.surface-yellow .tile-eyebrow { color: var(--color-black); opacity: 0.7; }
|
|
|
|
/* Checkmark list variant (for .surface-yellow with .checklist) */
|
|
.tile-body :global(.checklist) {
|
|
list-style: none;
|
|
padding: 0;
|
|
}
|
|
.tile-body :global(.checklist li) {
|
|
padding-left: 0;
|
|
}
|
|
</style>
|