mirror of
https://github.com/tomru/advcal.git
synced 2026-03-02 22:17:17 +01:00
first working version
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -32,3 +32,5 @@ yarn-error.log*
|
|||||||
|
|
||||||
# vercel
|
# vercel
|
||||||
.vercel
|
.vercel
|
||||||
|
.tool-versions
|
||||||
|
.log
|
||||||
|
|||||||
31
README.md
31
README.md
@@ -1,30 +1,7 @@
|
|||||||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
# Bello's Adventskalender
|
||||||
|
|
||||||
## Getting Started
|
# Getting Youtube playlist data
|
||||||
|
|
||||||
First, run the development server:
|
Get data in the [Api Explorer](https://developers.google.com/youtube/v3/docs/playlistItems/list?apix_params=%7B%22part%22%3A%5B%22snippet%22%5D%2C%22maxResults%22%3A24%2C%22playlistId%22%3A%22PLrhIlImke6F4uwg70DQmzGFOKIZdTpwPv%22%7D#usage)
|
||||||
|
|
||||||
```bash
|
Then pipe it to `./scripts/getSongs.sh` and copy it into `./pages/api/songs.js`
|
||||||
npm run dev
|
|
||||||
# or
|
|
||||||
yarn dev
|
|
||||||
```
|
|
||||||
|
|
||||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
|
||||||
|
|
||||||
You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
|
|
||||||
|
|
||||||
## Learn More
|
|
||||||
|
|
||||||
To learn more about Next.js, take a look at the following resources:
|
|
||||||
|
|
||||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
|
||||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
|
||||||
|
|
||||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
|
||||||
|
|
||||||
## Deploy on Vercel
|
|
||||||
|
|
||||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/import?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
|
||||||
|
|
||||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
|
||||||
|
|||||||
42
components/calendar.js
Normal file
42
components/calendar.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import React, { useContext } from "react";
|
||||||
|
|
||||||
|
import AppContext from "../context/app";
|
||||||
|
|
||||||
|
const Calendar = () => {
|
||||||
|
const { loading, songs, openDoor } = useContext(AppContext);
|
||||||
|
|
||||||
|
if (loading || !songs) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<ul className="cal">
|
||||||
|
{songs.map((song, index) => {
|
||||||
|
const classes = ["calcard", song.locked && "calcard__locked"]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(" ");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
className={classes}
|
||||||
|
data-id={index}
|
||||||
|
key={song.id}
|
||||||
|
onClick={() => !song.locked && openDoor(index)}
|
||||||
|
title={
|
||||||
|
song.locked &&
|
||||||
|
`Es ist noch nicht der ${new Date(
|
||||||
|
song.lockedUntil
|
||||||
|
).toLocaleDateString()}! Geduld, nur Gedult!` || index + 1
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span className="cardnumber">{index + 1}</span>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div >
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Calendar;
|
||||||
36
components/controls.js
vendored
Normal file
36
components/controls.js
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import React, { useEffect, useState, Fragment } from "react";
|
||||||
|
|
||||||
|
export const ENDED = 0;
|
||||||
|
export const PLAYING = 1;
|
||||||
|
export const PAUSED = 2;
|
||||||
|
|
||||||
|
const Controls = ({ playerState, onPause, onPlay, onRestart }) => {
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<div className="player-controls">
|
||||||
|
{playerState === PLAYING && (
|
||||||
|
<a onClick={onPause} title="Pause">
|
||||||
|
⏸
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{playerState === PAUSED && (
|
||||||
|
<a onClick={onPlay} title="Play">
|
||||||
|
▶
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{[PLAYING, PAUSED].includes(playerState) && (
|
||||||
|
<a onClick={onRestart} title="Von Vorne">
|
||||||
|
⏮
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{playerState === ENDED && (
|
||||||
|
<a onClick={onPlay} title="Play">
|
||||||
|
▶
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Controls;
|
||||||
27
components/header.js
Normal file
27
components/header.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
const Header = () => {
|
||||||
|
const [gifUrl, setGifUrl] = useState(null);
|
||||||
|
useEffect(() => {
|
||||||
|
async function getGif() {
|
||||||
|
const response = await fetch(
|
||||||
|
"//api.giphy.com/v1/gifs/random?tag=christmas&api_key=3ziHSa4ptYJdv2dOuawgzpBhhiW09Ss1"
|
||||||
|
);
|
||||||
|
const { data } = await response.json();
|
||||||
|
setGifUrl(data.image_mp4_url);
|
||||||
|
}
|
||||||
|
|
||||||
|
getGif();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="header">
|
||||||
|
<h1>Bello's Adventskalender 2020</h1>
|
||||||
|
{gifUrl && (
|
||||||
|
<video className="yay-gif-video" src={gifUrl} autoPlay loop muted />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Header;
|
||||||
28
components/nav.js
Normal file
28
components/nav.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const links = [
|
||||||
|
{ href: "https://youtube.com", label: "Youtube" },
|
||||||
|
{ href: "https://www.discogs.com/", label: "Discog" },
|
||||||
|
{ href: "https://findmusicbylyrics.com/", label: "Find music by Lyrics" },
|
||||||
|
{ href: "https://musicbrainz.org/", label: "MusicBrainz" }
|
||||||
|
].map(link => {
|
||||||
|
link.key = `nav-link-${link.href}-${link.label}`;
|
||||||
|
return link;
|
||||||
|
});
|
||||||
|
|
||||||
|
const Nav = () => (
|
||||||
|
<nav className="ad-nav">
|
||||||
|
<ul>
|
||||||
|
<li>Für die Recherche:</li>
|
||||||
|
{links.map(({ key, href, label }) => (
|
||||||
|
<li key={key}>
|
||||||
|
<a href={href} target="_blank">
|
||||||
|
{label}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Nav;
|
||||||
64
components/player.js
Normal file
64
components/player.js
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import React, { useEffect, useState, Fragment, useContext } from "react";
|
||||||
|
import youtubePlayer from "youtube-player";
|
||||||
|
|
||||||
|
import AppContext from "../context/app";
|
||||||
|
|
||||||
|
import Controls, { ENDED, PLAYING, PAUSED } from "./controls";
|
||||||
|
|
||||||
|
let player;
|
||||||
|
|
||||||
|
const Player = ({ hidden }) => {
|
||||||
|
const [playerState, setPlayerState] = useState(-1);
|
||||||
|
|
||||||
|
const { openSong } = useContext(AppContext);
|
||||||
|
|
||||||
|
const onPause = () => player && player.pauseVideo();
|
||||||
|
const onPlay = () => player && player.playVideo();
|
||||||
|
const onRestart = () => {
|
||||||
|
player && player.seekTo(openSong.startSeconds || 0);
|
||||||
|
player && [PAUSED, ENDED].includes(playerState) && player.playVideo();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(onRestart, [!hidden]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const setState = ({ data }) => setPlayerState(data);
|
||||||
|
player = youtubePlayer("player", {
|
||||||
|
height: "390",
|
||||||
|
width: "640"
|
||||||
|
});
|
||||||
|
player.on("stateChange", setState);
|
||||||
|
player.loadVideoById({
|
||||||
|
videoId: openSong.id,
|
||||||
|
startSeconds: openSong.startSeconds,
|
||||||
|
endSeconds: openSong.endSeconds
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
player = null;
|
||||||
|
setPlayerState(null);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<div className={hidden ? "hidden" : undefined}>
|
||||||
|
<div id="player" />
|
||||||
|
</div>
|
||||||
|
{hidden && playerState === -1 && <span>🎶 Titel laden...</span>}
|
||||||
|
{hidden && playerState > -1 && (
|
||||||
|
<Fragment>
|
||||||
|
<span>🎶 Titel spielt</span>
|
||||||
|
<Controls
|
||||||
|
playerState={playerState}
|
||||||
|
onPause={onPause}
|
||||||
|
onPlay={onPlay}
|
||||||
|
onRestart={onRestart}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Player;
|
||||||
57
components/showdoordialog.js
Normal file
57
components/showdoordialog.js
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import React, { useState, useContext } from "react";
|
||||||
|
|
||||||
|
import AppContext from "../context/app";
|
||||||
|
import Player from "./player";
|
||||||
|
|
||||||
|
const ShowDoorDialog = () => {
|
||||||
|
const { loading, openDoor, openSong, openSongIndex } = useContext(AppContext);
|
||||||
|
const [showSolution, setShowSolution] = useState(false);
|
||||||
|
const [showHint, setShowHint] = useState(false);
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
openDoor(null);
|
||||||
|
setShowSolution(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading || !openSong) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { title, hint, id } = openSong;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="door-mask" onClick={handleClose}>
|
||||||
|
<div className="door" onClick={event => event.stopPropagation()}>
|
||||||
|
<a className="door-close" onClick={handleClose}>
|
||||||
|
❌
|
||||||
|
</a>
|
||||||
|
<h1>Türchen {openSongIndex + 1}</h1>
|
||||||
|
<Player hidden={!showSolution} />
|
||||||
|
{!showSolution && (
|
||||||
|
<div>
|
||||||
|
{hint && !showHint && (
|
||||||
|
<p>
|
||||||
|
<a className="link" onClick={() => setShowHint(true)}>
|
||||||
|
Ich brauche einen Tip!
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{hint && showHint && (
|
||||||
|
<div className="hint">
|
||||||
|
<h4>Tip:</h4>
|
||||||
|
<p>{hint}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<p>
|
||||||
|
<a className="solve" onClick={() => setShowSolution(true)}>
|
||||||
|
Zeig mir die Lösung!
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ShowDoorDialog;
|
||||||
5
context/app.js
Normal file
5
context/app.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const AppContext = React.createContext();
|
||||||
|
|
||||||
|
export default AppContext;
|
||||||
50
context/appProvider.js
Normal file
50
context/appProvider.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
import AppContext from "./app";
|
||||||
|
|
||||||
|
const AppProvider = ({ debug, children }) => {
|
||||||
|
const [songs, setSongs] = useState(null);
|
||||||
|
const [openSong, setOpenSong] = useState(null);
|
||||||
|
|
||||||
|
const URL = debug ? "/api/songs?unlock=true" : "/api/songs";
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function getSongs() {
|
||||||
|
const response = await fetch(URL);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
setError(`HTTP Status of youtube request: ${response.status}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { songs } = await response.json();
|
||||||
|
setSongs(songs);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSongs();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const openDoor = index => {
|
||||||
|
if (!Number.isInteger(index) || !songs[index]) {
|
||||||
|
setOpenSong(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
setOpenSong(songs[index]);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppContext.Provider
|
||||||
|
value={{
|
||||||
|
loading: !songs,
|
||||||
|
songs,
|
||||||
|
openSong,
|
||||||
|
openSongIndex: songs && songs.indexOf(openSong),
|
||||||
|
openDoor
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</AppContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AppProvider;
|
||||||
2
next-env.d.ts
vendored
Normal file
2
next-env.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/// <reference types="next" />
|
||||||
|
/// <reference types="next/types/global" />
|
||||||
914
package-lock.json
generated
914
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -8,9 +8,14 @@
|
|||||||
"start": "next start"
|
"start": "next start"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"luxon": "^1.25.0",
|
||||||
"next": "10.0.3",
|
"next": "10.0.3",
|
||||||
|
"postcss-flexbugs-fixes": "^5.0.2",
|
||||||
|
"postcss-nested": "^5.0.1",
|
||||||
|
"postcss-preset-env": "^6.7.0",
|
||||||
"react": "17.0.1",
|
"react": "17.0.1",
|
||||||
"react-dom": "17.0.1"
|
"react-dom": "17.0.1",
|
||||||
|
"youtube-player": "^5.5.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^14.14.10",
|
"@types/node": "^14.14.10",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import '../styles/globals.css'
|
import '../styles/globals.css'
|
||||||
|
import "../styles/styles.css";
|
||||||
|
|
||||||
function MyApp({ Component, pageProps }) {
|
function MyApp({ Component, pageProps }) {
|
||||||
return <Component {...pageProps} />
|
return <Component {...pageProps} />
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
|
||||||
|
|
||||||
export default (req, res) => {
|
|
||||||
res.statusCode = 200
|
|
||||||
res.json({ name: 'John Doe' })
|
|
||||||
}
|
|
||||||
125
pages/api/songs.js
Normal file
125
pages/api/songs.js
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
const { DateTime } = require("luxon");
|
||||||
|
|
||||||
|
const SONGS = [
|
||||||
|
{
|
||||||
|
id: "Bys-OE_C7lQ",
|
||||||
|
title: "A Christmas Twist - Si Cranstoun",
|
||||||
|
startSeconds: 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "lheIRyhsUSE",
|
||||||
|
title: "EAV - Fata Morgana 1986",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Dqn01vCQnUs",
|
||||||
|
title: "Sheena Easton - My Baby Takes The Morning Train (with Lyric)HQ",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "b_BzMuOAKUs",
|
||||||
|
title: "Rod Stewart - Have I Told You Lately",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "3D-j9PcOPAk",
|
||||||
|
title: "Deifedanz",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "9HvpIgHBSdo",
|
||||||
|
title: 'Gregory Porter - "Be Good (Lion\'s Song)" Official Video',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "3FKA-3uRdQY",
|
||||||
|
title:
|
||||||
|
"Doris Day - Whatever Will Be Will Be Que Sera Sera (Best All Time Hits Forever 2014 / HQ) Mu©o",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "WIoHSu5v1Mo",
|
||||||
|
title: "The Specials - Message to you Rudy",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "6ZwjdGSqO0k",
|
||||||
|
title: "George Harrison - Got My Mind Set On You (Version I)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "lAj-Q_W9AT4",
|
||||||
|
title: "George Strait - Write This Down (Closed Captioned)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "YiadNVhaGwk",
|
||||||
|
title: "Chuck Berry - Run Rudolph Run (Official Video)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "_6FBfAQ-NDE",
|
||||||
|
title: "Depeche Mode - Just Can't Get Enough (Official Video)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "X22vAmpZSdY",
|
||||||
|
title: "MIEKE TELKAMP ★★★ TULPEN AUS AMSTERDAM ★★★ 1961",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "uvGvmsLQaHA",
|
||||||
|
title: "Elvis Presley - Green Green Grass Of Home (best video)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "5vheNbQlsyU",
|
||||||
|
title:
|
||||||
|
"Lady Gaga - Always Remember Us This Way (from A Star Is Born) (Official Music Video)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "4WM_R-6AKHE",
|
||||||
|
title: "Mockingbird - Carly Simon & James Taylor",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "MgIwLeASnkw",
|
||||||
|
title: "Elmo & Patsy - Grandma Got Run over by a Reindeer",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "SLErVV1TxUI",
|
||||||
|
title: "Gänsehaut - Karl der Käfer 1983",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "js-2cqqY1K8",
|
||||||
|
title: "The Beautiful South - Perfect 10 (Official Video)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "S6nSpBiekzQ",
|
||||||
|
title: "Les Négresses Vertes - Zobi La Mouche (Official Music Video)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "z0qW9P-uYfM",
|
||||||
|
title: "Elton John - Don't Go Breaking My Heart (with Kiki Dee)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Pgqa3cVOxUc",
|
||||||
|
title: "The Undertones - My Perfect Cousin (Official Video)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "lLLL1KxpYMA",
|
||||||
|
title: "Madness - Night Boat to Cairo (Official HD Video)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "qTx-sdR6Yzk",
|
||||||
|
title: 'Dropkick Murphys - "The Season\'s Upon Us" (Video)',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
const unlocked = !!req.query.unlock;
|
||||||
|
const NOW = DateTime.utc().setZone("Europe/Berlin");
|
||||||
|
|
||||||
|
const getLockedData = (index) => {
|
||||||
|
const dayString = ("00" + (index + 1)).substr(-2, 2);
|
||||||
|
const lockedUntilDateTime = DateTime.fromISO(
|
||||||
|
`${NOW.year}-12-${dayString}T00:00:00.000+01:00`
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
locked: unlocked ? false : NOW < lockedUntilDateTime,
|
||||||
|
lockedUntil: lockedUntilDateTime.toISO(),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const songs = SONGS.map((item, index) => ({
|
||||||
|
...item,
|
||||||
|
...getLockedData(index),
|
||||||
|
}));
|
||||||
|
res.json({ songs });
|
||||||
|
};
|
||||||
8
pages/debug.js
Normal file
8
pages/debug.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import React from "react";
|
||||||
|
import Home from "./index";
|
||||||
|
|
||||||
|
const HomeDebug = () => {
|
||||||
|
return <Home debug={true} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HomeDebug;
|
||||||
@@ -1,65 +1,25 @@
|
|||||||
import Head from 'next/head'
|
import Head from "next/head";
|
||||||
import styles from '../styles/Home.module.css'
|
|
||||||
|
|
||||||
export default function Home() {
|
import AppProvider from "../context/appProvider";
|
||||||
|
|
||||||
|
import Header from "../components/header";
|
||||||
|
import Calendar from "../components/calendar";
|
||||||
|
import ShowDoorDialog from "../components/showdoordialog";
|
||||||
|
import Nav from "../components/nav";
|
||||||
|
|
||||||
|
export default function Home({ debug = false }) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div>
|
||||||
<Head>
|
<Head>
|
||||||
<title>Create Next App</title>
|
<title>Bello's Adventskalender 2020</title>
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
</Head>
|
</Head>
|
||||||
|
<AppProvider debug={debug}>
|
||||||
<main className={styles.main}>
|
<Header />
|
||||||
<h1 className={styles.title}>
|
<Calendar />
|
||||||
Welcome to <a href="https://nextjs.org">Next.js!</a>
|
<ShowDoorDialog />
|
||||||
</h1>
|
<Nav />
|
||||||
|
</AppProvider>
|
||||||
<p className={styles.description}>
|
|
||||||
Get started by editing{' '}
|
|
||||||
<code className={styles.code}>pages/index.js</code>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className={styles.grid}>
|
|
||||||
<a href="https://nextjs.org/docs" className={styles.card}>
|
|
||||||
<h3>Documentation →</h3>
|
|
||||||
<p>Find in-depth information about Next.js features and API.</p>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="https://nextjs.org/learn" className={styles.card}>
|
|
||||||
<h3>Learn →</h3>
|
|
||||||
<p>Learn about Next.js in an interactive course with quizzes!</p>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a
|
|
||||||
href="https://github.com/vercel/next.js/tree/master/examples"
|
|
||||||
className={styles.card}
|
|
||||||
>
|
|
||||||
<h3>Examples →</h3>
|
|
||||||
<p>Discover and deploy boilerplate example Next.js projects.</p>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a
|
|
||||||
href="https://vercel.com/import?filter=next.js&utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
|
|
||||||
className={styles.card}
|
|
||||||
>
|
|
||||||
<h3>Deploy →</h3>
|
|
||||||
<p>
|
|
||||||
Instantly deploy your Next.js site to a public URL with Vercel.
|
|
||||||
</p>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer className={styles.footer}>
|
|
||||||
<a
|
|
||||||
href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Powered by{' '}
|
|
||||||
<img src="/vercel.svg" alt="Vercel Logo" className={styles.logo} />
|
|
||||||
</a>
|
|
||||||
</footer>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
18
postcss.config.json
Normal file
18
postcss.config.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"plugins": [
|
||||||
|
"postcss-nested",
|
||||||
|
"postcss-flexbugs-fixes",
|
||||||
|
[
|
||||||
|
"postcss-preset-env",
|
||||||
|
{
|
||||||
|
"autoprefixer": {
|
||||||
|
"flexbox": "no-2009"
|
||||||
|
},
|
||||||
|
"stage": 3,
|
||||||
|
"features": {
|
||||||
|
"custom-properties": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
5
scripts/getSongs.sh
Executable file
5
scripts/getSongs.sh
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Gets videoIds from a youtube playlistItems response provided on stdin.
|
||||||
|
|
||||||
|
jq -M '[.items[] | {id: .snippet.resourceId.videoId, title: .snippet.title }]' <&0
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
.container {
|
|
||||||
min-height: 100vh;
|
|
||||||
padding: 0 0.5rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main {
|
|
||||||
padding: 5rem 0;
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
width: 100%;
|
|
||||||
height: 100px;
|
|
||||||
border-top: 1px solid #eaeaea;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer img {
|
|
||||||
margin-left: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer a {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title a {
|
|
||||||
color: #0070f3;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title a:hover,
|
|
||||||
.title a:focus,
|
|
||||||
.title a:active {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
margin: 0;
|
|
||||||
line-height: 1.15;
|
|
||||||
font-size: 4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title,
|
|
||||||
.description {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description {
|
|
||||||
line-height: 1.5;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.code {
|
|
||||||
background: #fafafa;
|
|
||||||
border-radius: 5px;
|
|
||||||
padding: 0.75rem;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
|
||||||
Bitstream Vera Sans Mono, Courier New, monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
max-width: 800px;
|
|
||||||
margin-top: 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
margin: 1rem;
|
|
||||||
flex-basis: 45%;
|
|
||||||
padding: 1.5rem;
|
|
||||||
text-align: left;
|
|
||||||
color: inherit;
|
|
||||||
text-decoration: none;
|
|
||||||
border: 1px solid #eaeaea;
|
|
||||||
border-radius: 10px;
|
|
||||||
transition: color 0.15s ease, border-color 0.15s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card:hover,
|
|
||||||
.card:focus,
|
|
||||||
.card:active {
|
|
||||||
color: #0070f3;
|
|
||||||
border-color: #0070f3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card h3 {
|
|
||||||
margin: 0 0 1rem 0;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card p {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 1.25rem;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
height: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
.grid {
|
|
||||||
width: 100%;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
180
styles/styles.css
Normal file
180
styles/styles.css
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
:root {
|
||||||
|
--bg-color: #1b5e20;
|
||||||
|
|
||||||
|
--text-color: #fff;
|
||||||
|
|
||||||
|
--link-color: var(--text-color);
|
||||||
|
--link-color--hover: #000;
|
||||||
|
|
||||||
|
--calcard-color: #fff;
|
||||||
|
--calcard-bg-color: #c62828;
|
||||||
|
--calcard-bg-color__locked: #003300;
|
||||||
|
|
||||||
|
--door-bg-color: #4c8c4a;
|
||||||
|
--door-color: #fff;
|
||||||
|
|
||||||
|
--player-controls-bg-color: #1b5e20;
|
||||||
|
|
||||||
|
--door-hint-bg-color: #c62828;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-family: "Charm", cursive;
|
||||||
|
font-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 2rem 12rem;
|
||||||
|
|
||||||
|
color: var(--text-color);
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--link-color);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--link-color--hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
position: absolute;
|
||||||
|
left: -5000px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
flex-grow: 1;
|
||||||
|
font-size: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yay-gif-video {
|
||||||
|
max-height: 12rem;
|
||||||
|
max-width: 20rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cal {
|
||||||
|
padding-left: 0;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(6, 1fr);
|
||||||
|
grid-template-rows: repeat(4, 6vw);
|
||||||
|
grid-gap: 2rem;
|
||||||
|
|
||||||
|
.calcard {
|
||||||
|
color: var(--calcard-color);
|
||||||
|
background-color: var(--calcard-bg-color);
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 2px 2px 8px 3px #000;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&__locked {
|
||||||
|
background-color: var(--calcard-bg-color__locked);
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .cardnumber {
|
||||||
|
font-size: 6vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.door-mask {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
background-color: rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.door {
|
||||||
|
background-color: var(--door-bg-color);
|
||||||
|
color: var(--door-color);
|
||||||
|
|
||||||
|
padding: 2rem;
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1.7rem;
|
||||||
|
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 2px 2px 8px 3px #000;
|
||||||
|
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
|
||||||
|
min-width: 30rem;
|
||||||
|
|
||||||
|
.solve {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hint {
|
||||||
|
margin-top: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
background-color: var(--door-hint-bg-color);
|
||||||
|
border-radius: 3px;
|
||||||
|
box-shadow: 2px 2px 8px 3px #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.door-close {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
top: 0.5rem;
|
||||||
|
right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-controls {
|
||||||
|
display: inline-flex;
|
||||||
|
|
||||||
|
a {
|
||||||
|
width: 2rem;
|
||||||
|
margin-left: 16px;
|
||||||
|
padding: 0 6px;
|
||||||
|
background-color: var(--player-controls-bg-color);
|
||||||
|
border-radius: 3px;
|
||||||
|
box-shadow: 2px 2px 3px 2px #000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ad-nav {
|
||||||
|
& nav {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
& ul {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
& nav > ul {
|
||||||
|
padding: 4px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& li {
|
||||||
|
display: flex;
|
||||||
|
padding: 6px 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user