Custom Code Experiment •
Indonesian Greetings
Source Code View HTML / CSS / JavaScript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<title>Indonesian Greetings</title>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<script src="https://cdn.jsdelivr.net/npm/react@17.0.2/umd/react.development.js"></script>
<script src="https://cdn.jsdelivr.net/npm/react-dom@17.0.2/umd/react-dom.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/4.1.0/immutable.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<style>
* {
position: relative;
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
background: #121212;
font-family: 'Roboto', sans-serif;
height: 100%;
width: 100%;
}
* {
font-family: 'Roboto', sans-serif;
font-weight: 300;
}
.wrapper {
width: 100%;
height: 100vh;
overflow-x: hidden;
}
.content-wrapper {
display: flex;
height: 93vh;
justify-content: center;
align-items: center;
background: #121212;
margin: 0 auto;
padding: 0 2em;
}
.card {
width: 30em;
height: 15em;
display: flex;
justify-content: center;
align-items: center;
background: #1e1e2f;
box-shadow: 0 8px 24px rgba(0,0,0,0.5);
padding: 1em 3em;
transform-style: preserve-3d;
margin-top: 4em;
transition: 0.2s;
cursor: pointer;
border-radius: 20px;
border: 1px solid #333340;
}
@media(max-width: 600px) {
.card {
width: 25em;
}
}
@media(max-height: 823px) {
.card {
margin-top: 9em;
}
}
.card__flip-card {
position: absolute;
top: 4%;
right: 5%;
cursor: pointer;
color: #aaa;
font-size: 1.2rem;
transition: 0.2s;
z-index: 5;
}
.card__flip-card:hover {
color: #ddd;
}
.card__counter {
position: absolute;
left: 5%;
top: 5%;
font-size: 0.85rem;
background: #2c2c3a;
padding: 0.2rem 0.6rem;
border-radius: 20px;
color: #ccccdd;
font-weight: 400;
}
.card__content--front {
animation: fadeInUp 0.5s ease;
font-weight: 500;
font-size: 1.8rem;
text-align: center;
word-break: break-word;
color: #f0f0f0;
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
}
.card__content--back {
animation: fadeIn 0.6s ease;
line-height: 1.5;
font-size: 1rem;
text-align: center;
word-break: break-word;
max-height: 90%;
overflow-y: auto;
color: #cfcfdf;
padding: 0 0.2rem;
}
.header {
background: #1a1a2a;
box-shadow: 0 4px 16px rgba(0,0,0,0.4);
padding: 1em 0em;
display: flex;
align-items: center;
justify-content: center;
height: 7vh;
color: #e0e0e0;
position: absolute;
width: 100%;
z-index: 10;
top: 0;
left: 0;
}
.header-content {
width: 100%;
display: flex;
justify-content: center;
font-weight: 400;
font-size: 1.3rem;
letter-spacing: 0.5px;
}
.header-content span {
font-size: 1.1rem;
}
.header-content__left, .header-content__right {
display: none;
}
.card-container__icon {
display: none;
}
.card__actions {
position: absolute;
bottom: 6%;
display: flex;
width: 100%;
justify-content: center;
opacity: 0;
transition: 0.3s;
left: 0;
gap: 0.75rem;
pointer-events: none;
}
.card__actions.active {
opacity: 1;
pointer-events: auto;
}
.card__next-button, .card__prev-button {
cursor: pointer;
padding: 0.5rem 1rem;
background: #2d2d3a;
color: #eaeef5;
transition: 0.2s;
border-radius: 40px;
font-weight: 400;
font-size: 0.85rem;
backdrop-filter: blur(2px);
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
border: 1px solid #3a3a4a;
}
.card__next-button:hover, .card__prev-button:hover {
background: #3e3e52;
color: white;
}
.card-container__dots-wrapper {
padding: 0.8rem 0rem;
margin-top: 1rem;
text-align: center;
display: flex;
justify-content: center;
gap: 0.6rem;
}
.card-container__dot {
color: #4a4a60;
cursor: pointer;
font-size: 0.8rem;
transition: 0.2s;
}
.card-container__dot.active {
color: #5f9eff;
text-shadow: 0 0 2px #3f6bc0;
transform: scale(1.2);
}
.card-container__dot:hover {
color: #9bb8ff;
}
@keyframes fadeInUp {
0% {
opacity: 0;
transform: translateY(20px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.create-card, .create-card__shadow, .create-card__body {
display: none !important;
}
</style>
</head>
<body>
<div id='app'></div>
<script type="text/babel">
class Header extends React.Component {
render() {
return (
<div className='header'>
<div className='header-content'>
<span>🇮🇩 Bahasa Indonesia • Greetings</span>
</div>
</div>
);
}
}
class Card extends React.Component {
constructor(props) {
super(props);
this.state = {
showAnswer: false
};
}
render() {
const content = this.state.showAnswer ? this.props.backContent : this.props.frontContent;
const iconClass = this.state.showAnswer ? 'reply' : 'share';
const cardClass = this.state.showAnswer ? 'back' : '';
const contentClass = this.state.showAnswer ? 'back' : 'front';
const actionClass = this.state.showAnswer ? 'active' : '';
const handleFlipClick = (e) => {
e.stopPropagation();
this.setState({ showAnswer: !this.state.showAnswer });
};
const handlePrev = (e) => {
e.stopPropagation();
this.props.showPrevCard();
this.setState({ showAnswer: false });
};
const handleNext = (e) => {
e.stopPropagation();
this.props.showNextCard();
this.setState({ showAnswer: false });
};
return (
<div
className={`card ${cardClass}`}
onClick={() => this.setState({ showAnswer: !this.state.showAnswer })}
>
<span className='card__counter'>{this.props.cardNumber + 1}</span>
<div
className='card__flip-card'
onClick={handleFlipClick}
>
<span className={`fa fa-${iconClass}`} />
</div>
<div className={`card__content--${contentClass}`}>
{content}
</div>
<div className={`card__actions ${actionClass}`}>
<div
className='card__prev-button'
onClick={handlePrev}
>
← Prev
</div>
<div
className='card__next-button'
onClick={handleNext}
>
Next →
</div>
</div>
</div>
);
}
}
class CardContainer extends React.Component {
constructor(props) {
super(props);
const initialData = Immutable.fromJS([
{
word: 'Selamat Pagi',
description: 'Good morning. 🇮🇩 Used from sunrise until about 11 AM. Polite and warm. Pronunciation: Se-la-mat Pa-gi (soft “t”).'
},
{
word: 'Selamat Siang',
description: 'Good day / Early afternoon. 🇮🇩 11 AM – 3 PM. Bright, formal greeting. Pronunciation: Se-la-mat See-ang.'
},
{
word: 'Selamat Sore',
description: 'Good afternoon. 🇮🇩 From 3 PM to sunset (around 6 PM). A pleasant late-day greeting. Pronunciation: Se-la-mat So-ray.'
},
{
word: 'Selamat Malam',
description: 'Good evening / Good night. 🇮🇩 After sunset (from 6 PM onward). Used to greet at night. Pronunciation: Se-la-mat Ma-lam.'
},
{
word: 'Apa Kabar?',
description: 'How are you? 🇮🇩 Literally “What’s the news?” A standard, caring question after greeting. Reply: “Kabar baik” (I’m fine). Pronunciation: Ah-pah Kah-bar.'
},
{
word: 'Kabar Baik',
description: 'I am fine / Good news. 🇮🇩 Positive response to “Apa Kabar?”. Also “Baik-baik saja” (just fine). Pronunciation: Kah-bar Ba-ik.'
},
{
word: 'Mau ke mana?',
description: 'Where are you going? 🇮🇩 Friendly cultural greeting, not invasive. Common reply: “Jalan-jalan saja” (just strolling). Pronunciation: Ma-oo ke mah-nah?'
},
{
word: 'Terima Kasih',
description: 'Thank you. 🇮🇩 Literally “receive love”. Essential politeness. Casual: “Makasih”. Pronunciation: Te-ree-mah Kah-see.'
},
{
word: 'Sama-sama',
description: 'You’re welcome. 🇮🇩 The standard, warm reply to “terima kasih”. Pronunciation: Sah-mah Sah-mah.'
},
{
word: 'Hati-hati',
description: 'Take care / Be careful. 🇮🇩 A kind farewell, especially when someone is travelling. Pronunciation: Hah-tee Hah-tee.'
}
]);
this.state = {
cards: initialData,
cardNumber: 0,
};
this.showPrevCard = this.showPrevCard.bind(this);
this.showNextCard = this.showNextCard.bind(this);
}
showNextCard() {
const nextIndex = this.state.cardNumber + 1;
if (nextIndex < this.state.cards.size) {
this.setState({ cardNumber: nextIndex });
}
}
showPrevCard() {
const prevIndex = this.state.cardNumber - 1;
if (prevIndex >= 0) {
this.setState({ cardNumber: prevIndex });
}
}
generateDots() {
const total = this.state.cards.size;
const currentIdx = this.state.cardNumber;
let dots = [];
for (let i = 0; i < total; i++) {
const dotClass = i === currentIdx ? 'active' : '';
dots.push(
<span
key={`dot-${i}`}
className={`card-container__dot fa fa-circle ${dotClass}`}
onClick={() => this.setState({ cardNumber: i })}
/>
);
}
return dots;
}
generateCurrentCard() {
const cardsList = this.state.cards;
if (!cardsList.size) return null;
const currentCard = cardsList.get(this.state.cardNumber);
if (!currentCard) return null;
return (
<Card
key={this.state.cardNumber}
frontContent={currentCard.get('word')}
backContent={currentCard.get('description')}
showNextCard={this.showNextCard}
showPrevCard={this.showPrevCard}
cardNumber={this.state.cardNumber}
/>
);
}
render() {
return (
<div style={{ width: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
{this.generateCurrentCard()}
<div className='card-container__dots-wrapper'>
{this.generateDots()}
</div>
</div>
);
}
}
class Main extends React.Component {
render() {
return (
<div className='wrapper'>
<Header />
<div className='content-wrapper'>
<CardContainer />
</div>
</div>
);
}
}
ReactDOM.render(<Main />, document.getElementById('app'));
</script>
</body>
</html>