Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6a44a847cd |
96
.gitignore
vendored
96
.gitignore
vendored
@@ -1,67 +1,41 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
node_modules_pc/
|
||||
node_modules_surface/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
/package-lock.json
|
||||
/nbproject/private/
|
||||
.vscode
|
||||
.serverless
|
||||
.cache
|
||||
yarn.lock
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
36
README.md
Normal file
36
README.md
Normal file
@@ -0,0 +1,36 @@
|
||||
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||
|
||||
## 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/new?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/app/building-your-application/deploying) for more details.
|
||||
8
next.config.ts
Normal file
8
next.config.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
reactCompiler: true,
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
1025
package-lock.json
generated
Normal file
1025
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
28
package.json
28
package.json
@@ -1,19 +1,23 @@
|
||||
{
|
||||
"name": "domsplace",
|
||||
"version": "7.0.0",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start:dev": "nodemon --exec ts-node ./src/index.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^5.0.6",
|
||||
"@types/node": "^25.5.0",
|
||||
"nodemon": "^3.1.14",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.9.3"
|
||||
"start:dev": "next dev",
|
||||
"build:prod": "next build",
|
||||
"start:prod": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^5.2.1"
|
||||
"next": "16.2.2",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4",
|
||||
"sass": "^1.98.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"babel-plugin-react-compiler": "1.0.0",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
|
||||
1
public/file.svg
Normal file
1
public/file.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 391 B |
1
public/globe.svg
Normal file
1
public/globe.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
1
public/next.svg
Normal file
1
public/next.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
1
public/vercel.svg
Normal file
1
public/vercel.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 128 B |
1
public/window.svg
Normal file
1
public/window.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
||||
|
After Width: | Height: | Size: 385 B |
BIN
src/app/favicon.ico
Normal file
BIN
src/app/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
7
src/app/globals.scss
Normal file
7
src/app/globals.scss
Normal file
@@ -0,0 +1,7 @@
|
||||
@use '@/styles/elements/root';
|
||||
|
||||
@use '@/styles/elements/a';
|
||||
@use '@/styles/elements/all';
|
||||
@use '@/styles/elements/button';
|
||||
@use '@/styles/elements/body';
|
||||
@use '@/styles/elements/html';
|
||||
23
src/app/layout.tsx
Normal file
23
src/app/layout.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { Metadata } from "next";
|
||||
import "./globals.scss";
|
||||
import { Header } from "@/components/Header/Header";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
<Header />
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
11
src/app/page.tsx
Normal file
11
src/app/page.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
const Home:React.FC<{}> = () => {
|
||||
return (
|
||||
<main>
|
||||
Homepage
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
export default Home;
|
||||
59
src/components/Header.scss
Normal file
59
src/components/Header.scss
Normal file
@@ -0,0 +1,59 @@
|
||||
|
||||
.header {
|
||||
margin-bottom: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-radius: var(--radius-large);
|
||||
border: 1px solid var(--color-neumorph-light);
|
||||
background: var(--color-header-bg);
|
||||
padding: 1rem 1.5rem;
|
||||
box-shadow:
|
||||
var(--color-header-shadow1),
|
||||
var(--color-header-shadow2),
|
||||
var(--color-header-shadow3);
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.header__brand {}
|
||||
.header__subtitle {
|
||||
color: var(--color-header-subtitle);
|
||||
}
|
||||
.header__title {
|
||||
color: var(--color-header-title);
|
||||
}
|
||||
|
||||
.header__nav {
|
||||
display: none;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.header__nav {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.header__nav-btn {
|
||||
border-radius: 9999px;
|
||||
border: 1px solid var(--color-neumorph-light);
|
||||
background: var(--color-header-btn-bg);
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-header-btn-text);
|
||||
box-shadow:
|
||||
var(--color-header-btn-shadow1),
|
||||
var(--color-header-btn-shadow2);
|
||||
transition: color 0.2s, box-shadow 0.2s;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: var(--color-neumorph-light);
|
||||
}
|
||||
|
||||
.header__nav-btn:hover {
|
||||
color: var(--color-header-btn-hover-text);
|
||||
box-shadow:
|
||||
var(--color-header-btn-hover-shadow1),
|
||||
var(--color-header-btn-hover-shadow2);
|
||||
}
|
||||
29
src/components/Header/Header.module.scss
Normal file
29
src/components/Header/Header.module.scss
Normal file
@@ -0,0 +1,29 @@
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 1rem 1.5rem;
|
||||
border-radius: var(--radius-large);
|
||||
border: 1px solid var(--color-neumorph-light);
|
||||
background: var( --color-surface);
|
||||
box-shadow: var(--box-shadow-deep);
|
||||
max-width: var(--max-width-large);
|
||||
margin: auto auto 2rem auto;
|
||||
|
||||
&__brand {
|
||||
|
||||
}
|
||||
|
||||
&__nav {
|
||||
display: none;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
&__nav {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/components/Header/Header.tsx
Normal file
21
src/components/Header/Header.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import HeaderButton from './HeaderButton';
|
||||
import styles from './Header.module.scss';
|
||||
|
||||
export const Header: React.FC<{}> = () => {
|
||||
return (
|
||||
<header className={styles['header']}>
|
||||
<div className={styles['header__brand']}>
|
||||
|
||||
</div>
|
||||
<nav className={styles['header__nav']}>
|
||||
{['Home', 'Features', 'Work', 'Contact'].map((item) => (
|
||||
<HeaderButton
|
||||
key={item}
|
||||
>
|
||||
{item}
|
||||
</HeaderButton>
|
||||
))}
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
14
src/components/Header/HeaderButton.module.scss
Normal file
14
src/components/Header/HeaderButton.module.scss
Normal file
@@ -0,0 +1,14 @@
|
||||
.HeaderButton {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
border-radius: 999em;
|
||||
background: var(--color-surface);
|
||||
box-shadow: var(--box-shadow-raised-low);
|
||||
border: 1px solid var(--color-neumorph-light);
|
||||
transition: all 1s ease;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
box-shadow: var(--box-shadow-lowered-low);
|
||||
}
|
||||
}
|
||||
14
src/components/Header/HeaderButton.tsx
Normal file
14
src/components/Header/HeaderButton.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import styles from './HeaderButton.module.scss';
|
||||
|
||||
const HeaderButton:React.FC<{
|
||||
children:React.ReactNode;
|
||||
}> = ({ children }) => {
|
||||
return (
|
||||
<button className={styles.HeaderButton}>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export default HeaderButton;
|
||||
11
src/index.ts
11
src/index.ts
@@ -1,11 +0,0 @@
|
||||
import express from 'express';
|
||||
import router from './routes';
|
||||
|
||||
const app = express();
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
app.use(router);
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Server is running on port ${port}`);
|
||||
});
|
||||
@@ -1,9 +0,0 @@
|
||||
export const LANGUAGES = <const>{
|
||||
'en': 'English'
|
||||
};
|
||||
|
||||
export type LocaleLanguage = keyof typeof LANGUAGES;
|
||||
|
||||
export type LocaleString = {
|
||||
[K in LocaleLanguage]:string;
|
||||
};
|
||||
28
src/page.ts
28
src/page.ts
@@ -1,28 +0,0 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { SectionData, sectionRender } from "./section";
|
||||
import TemplateDefault from "./templates/default";
|
||||
import { templateRender } from './template';
|
||||
import { LocaleString } from './locale';
|
||||
|
||||
export type Page = {
|
||||
title?:LocaleString;
|
||||
sections:SectionData<any>[];
|
||||
}
|
||||
|
||||
export const pageRoute = (page:Page) => {
|
||||
return async (req:Request, res:Response) => {
|
||||
console.log(`Got Request. Header Agent: ${req.headers['user-agent']}`);
|
||||
try {
|
||||
const content = await templateRender({
|
||||
page,
|
||||
template: TemplateDefault,
|
||||
request: req,
|
||||
language: 'en'
|
||||
});
|
||||
|
||||
res.status(200).send(content);
|
||||
} catch (error) {
|
||||
res.status(500).send(`Internal Server Error`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { Router } from "express";
|
||||
import { Page, pageRoute } from "../../page";
|
||||
import SnesDigitalAudioMod from "./snes-digital-audio-mod";
|
||||
|
||||
const ThisPage:Page = {
|
||||
sections: [
|
||||
|
||||
]
|
||||
};
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get('/', pageRoute(ThisPage));
|
||||
router.get('/snes-digital-audio-mod', SnesDigitalAudioMod);
|
||||
|
||||
export default router;
|
||||
@@ -1,147 +0,0 @@
|
||||
import { Router } from "express";
|
||||
import { Page, pageRoute } from "../../../page";
|
||||
|
||||
/*
|
||||
# Super Famicom / SNES Digital Audio Mod
|
||||
|
||||
Recently I have been revisiting some of my favorite retro game consoles, mostly
|
||||
due to reorganising my loungeroom. Probably one I wish I spent more time playing
|
||||
was the Super Nintendo, and I wanted to address some of the problems with my Japanese Super Famicom (SFC).
|
||||
|
||||
Primarily the problems stemmed from less than ideal quality, this is due to the
|
||||
SNES's well known [terrible image softening](https://www.chrismcovell.com/gotRGB/snesblur.html)
|
||||
and that my SFC was pretty yellowed.
|
||||
|
||||
Second issue was my audio. I have some somewhat decent SCART cables I use but
|
||||
the static caused by interference from the analogue audio was definitely not
|
||||
ideal. I wanted to bypass the SNES's analogue audio, and hear the digital audio
|
||||
from the console. To achieve this I needed to perform a digital audio mod.
|
||||
|
||||
Finally, I wanted to challenge my soldering skills a bit more, and so I decided
|
||||
to tackle these issues all at the same time.
|
||||
|
||||
## The plan
|
||||
|
||||
To address the image quality issue, I wanted to get a [1chip SFC](https://consolemods.org/wiki/SNES:SNES_Model_Differences#Comparisons).
|
||||
These 1chip systems are named after their motherboard, which were the names
|
||||
used on later revision motherboards that had noticeably improved image quality.
|
||||
These later revisions integrate the video circuitry into a single chip, reducing
|
||||
signal noise and resulting in a noticeably sharper image while retaining RGB output
|
||||
|
||||
Second, I had been aware of Digital Audio Mods for the SNES for a while, but
|
||||
they all typically involved cutting the case of the system to accommodate a full
|
||||
TOSLINK connector, which I wanted to avoid doing where possible, and keep the
|
||||
original case intact.
|
||||
|
||||
Finally, I had been made aware that [The Retro Channel](https://www.chrismcovell.com/gotRGB/snesblur.html)
|
||||
had a no-cut SNES digital audio mod, that replaced the RF module of the SNES, and
|
||||
did not require cutting.
|
||||
|
||||
## New Super Famicom
|
||||
|
||||
There are really only three ways to get a 1chip SNES;
|
||||
1) Run the motherboard lottery, where you purchase a SNES, then have to open it and check if it is a
|
||||
1chip variant or not.
|
||||
2) Purchase a SNES/SFC Jr. and mod it for RGB support.
|
||||
3) Purchase a 1chip SNES/SFC from a reseller.
|
||||
|
||||
I decided to go with option 3, really wanted to keep the original SFC and didn't
|
||||
want to spend a fortune trying to find a 1chip myself. I ended up purchasing a
|
||||
1chip SFC from an eBay reseller for around $100 USD, not too bad considering they
|
||||
can go for significantly more.
|
||||
|
||||
Originally I had planned to also do a full recap of the system, to extend its
|
||||
life. This is definitely a moment where I realised that buying a 1chip from a
|
||||
reseller was maybe not the best idea.
|
||||
|
||||
Upon opening the system I found it had been recapped already, but the quality of
|
||||
the work left a lot to be desired. The recap solder points were very messy with
|
||||
way too much solder remaining on the board. The legs of the caps were also left
|
||||
rather long and get close to interfering with the RF Shield. I have not yet
|
||||
recapped the system but I definitely plan to do so in the near future.
|
||||
|
||||
Other than the iffy recap job however, the system worked fine and the image
|
||||
quality compared to my previous SFC was significantly improved.
|
||||
|
||||
## Digital Audio Mod
|
||||
|
||||
The SNES typically outputs line level audio through the AV port in stereo. This
|
||||
is fine but the Digital Signal Processor (DSP) chip in the SNES is capable of
|
||||
producing much higher quality audio, and several games use the full [32 KHz sample rate](https://www.alpha-ii.com/Info/snes-spdif.html)
|
||||
that the system is capable of, but the audio the analogue output provides is
|
||||
[significantly more limited](https://www.youtube.com/watch?v=6J7Sea0KniU&t=98s).
|
||||
|
||||
By the time I decide to purchase my 1chip, the no cut mods had sold out
|
||||
unfortunately, which delayed me initially. After a few weeks however The Retro
|
||||
Channel had created a new version, the [No-Cut Digital Audio Mod v2](https://lectronz.com/products/super-nintendo-digital-audio-no-cut-mod-v2)
|
||||
|
||||
The v1 version of the mod took over the RF connector and turned it into a
|
||||
digital coaxial output, meaning that from the outside the console looked
|
||||
completely stock. The new v2 version removed the entire RF module and replaced
|
||||
it with a small PCB that doubled as a 3.5mm coaxial and mini-TOSLINK output,
|
||||
meaning that the console looked slightly different but still required no cutting
|
||||
of the case, which is nice.
|
||||
|
||||
I do wish I could have purchased one of the original v1 mods, but the v2 was
|
||||
available and functionally is the same, so I purchased it.
|
||||
|
||||
## Installation
|
||||
|
||||
Installation was pretty straight forward, mostly following [The Retro Channel's video](https://www.youtube.com/watch?v=OXpKuyHBA48)
|
||||
I was able to tackle it in an afternoon. The kit comes with all the parts you
|
||||
need and it took me around an hour to install, taking my time and testing after
|
||||
each solder to ensure no shorts or bad connections.
|
||||
|
||||
The only difficult part was soldering the three wires to the DSP chip, as they
|
||||
are very close and keeping the legs apart was a bit tricky, but with patience I
|
||||
got the soldering done without any issues.
|
||||
|
||||
## Results
|
||||
|
||||
From the outside it is clear the Super Famicom has been modded, but the mod is
|
||||
otherwise clean and there's no damage to the case, so it looks good. I attached
|
||||
a mini-TOSLINK to full size TOSLINK adapter, which hides the smaller 3.5mm size
|
||||
of the connector and keeps it looking cleaner.
|
||||
|
||||
As for the audio, it's fantastic. This is by far the best sounding SNES audio I
|
||||
have ever heard. I was worried my Sony STR-DN1040 would not like the SNES digital audio signal,
|
||||
since pauses in the audio count as the digital audio stopping, but it handles it
|
||||
fine and I've heard no stutters or pauses.
|
||||
|
||||
Finally I would be remiss if I did not mention the downsides. Really there is
|
||||
only two. The obvious is the cost; the 1chip itself is expensive for a SNES and
|
||||
the mod was also not cheap, then the time it took for me to install the mod was
|
||||
not insignificant.
|
||||
|
||||
The second drawback is that the mod only provides digital audio on the audio
|
||||
generated by the SNES's internal DSP chip. This is rare but the SNES could allow
|
||||
games to perform their own audio processing, bypassing the SNES DSP chip entirely and
|
||||
therefore not outputting through the mod. The only notable instances of this are
|
||||
the Super Gameboy, which used a custom chip to emulate the Gameboy's audio on
|
||||
the Super Gameboy Cartridge itself, bypassing the SNES DSP, and any games that
|
||||
make use of the custom MSU-1 chip.
|
||||
|
||||
## Surround Sound
|
||||
|
||||
I want to do a full post on this in the future, but the SNES supported Dolby Pro Logic
|
||||
surround sound in some games. I have yet to find a comprehensive list but definitely
|
||||
Star Ocean supports it, and uses it very effectively. Over the digital audio the
|
||||
surround is very clear and has a wide soundstage, it's extremely impressive for a
|
||||
16-bit console.
|
||||
|
||||
## Conclusion
|
||||
This was an expensive and time consuming mod, but it is about as close to the
|
||||
perfect SNES as one can get. The only other mods I am aware of that could improve it are;
|
||||
a better RGB bypass mod, similar to what the N64 RGB mods use, or a pure digital
|
||||
video mod, similar to the [RetroGEM](https://www.pixelfx.co/product-page/n64-hdmi)
|
||||
mods, but I am not aware of any for the SNES currently.
|
||||
*/
|
||||
|
||||
const BlogPage:Page = {
|
||||
sections: [
|
||||
]
|
||||
};
|
||||
|
||||
const router = Router();
|
||||
router.get('/', pageRoute(BlogPage));
|
||||
export default router;
|
||||
@@ -1,31 +0,0 @@
|
||||
import { Router } from 'express';
|
||||
import { Page, pageRoute } from '../page';
|
||||
import BlogRoute from './blog';
|
||||
|
||||
const HomePage:Page = {
|
||||
sections:[
|
||||
{
|
||||
type: 'hero',
|
||||
properties: {
|
||||
title: 'Dominic Masters\nSoftware Developer and Tinkerer.',
|
||||
subtitle: `I develop all manner of things, and tinker with tech new and old.`,
|
||||
buttonLeft: {
|
||||
text: `View the blog`,
|
||||
url: `/blog`
|
||||
},
|
||||
buttonRight: {
|
||||
text: `About me`,
|
||||
url: `/about`
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get('/', pageRoute(HomePage));
|
||||
router.get('/blog', BlogRoute);
|
||||
|
||||
export default router;
|
||||
@@ -1,57 +0,0 @@
|
||||
import { Request } from 'express';
|
||||
import HeroSection from './sections/hero';
|
||||
import { Template } from './template';
|
||||
import { LocaleLanguage } from './locale';
|
||||
import { Page } from './page';
|
||||
|
||||
const SECTION_TYPES = <const>{
|
||||
'hero': HeroSection
|
||||
}
|
||||
|
||||
export type Section<P> = {
|
||||
properties:P;
|
||||
validate:(properties:P) => P;
|
||||
};
|
||||
|
||||
export type SectionType = keyof typeof SECTION_TYPES;
|
||||
|
||||
export type SectionTypeFor<T extends SectionType> = (
|
||||
typeof SECTION_TYPES[T]
|
||||
);
|
||||
|
||||
export type SectionProperties<T extends SectionType> = (
|
||||
SectionTypeFor<T>['properties']
|
||||
);
|
||||
|
||||
export type SectionData<T extends SectionType> = {
|
||||
type:T;
|
||||
properties:SectionProperties<T>;
|
||||
}
|
||||
|
||||
export type SectionRenderer<T extends SectionType> = (p:{
|
||||
properties:SectionProperties<T>;
|
||||
template:Template;
|
||||
language:LocaleLanguage;
|
||||
request:Request;
|
||||
page:Page;
|
||||
}) => Promise<string>;
|
||||
|
||||
export const sectionRender = async <T extends SectionType>(p:{
|
||||
request:Request,
|
||||
section:SectionData<T>;
|
||||
template:Template;
|
||||
language:LocaleLanguage;
|
||||
page:Page;
|
||||
}):Promise<string> => {
|
||||
if(!p.template.sections[p.section.type]) {
|
||||
console.warn(`No section renderer found for section type "${p.section.type}" in template "${p.template.name}".`);
|
||||
return '';
|
||||
}
|
||||
|
||||
const renderer = p.template.sections[p.section.type] as SectionRenderer<T>;
|
||||
const properties = p.section.properties;
|
||||
return await renderer({
|
||||
...p,
|
||||
properties: p.section.properties
|
||||
});
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { Section } from "../section";
|
||||
|
||||
type HeroProperties = {
|
||||
title:string|null;
|
||||
subtitle:string|null;
|
||||
buttonLeft?:{
|
||||
text:string;
|
||||
url:string;
|
||||
};
|
||||
buttonRight?:{
|
||||
text:string;
|
||||
url:string;
|
||||
};
|
||||
};
|
||||
|
||||
const HERO:Section<HeroProperties> = {
|
||||
properties: {
|
||||
title: '',
|
||||
subtitle: '',
|
||||
|
||||
},
|
||||
|
||||
validate: props => {
|
||||
if(!props.title) throw new Error('Hero section must have a title.');
|
||||
return props;
|
||||
}
|
||||
};
|
||||
|
||||
export default HERO;
|
||||
4
src/styles/elements/a.scss
Normal file
4
src/styles/elements/a.scss
Normal file
@@ -0,0 +1,4 @@
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
3
src/styles/elements/all.scss
Normal file
3
src/styles/elements/all.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
16
src/styles/elements/body.scss
Normal file
16
src/styles/elements/body.scss
Normal file
@@ -0,0 +1,16 @@
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 1rem;
|
||||
max-width: 100vw;
|
||||
overflow-x: hidden;
|
||||
min-height: 100%;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
|
||||
background-color: var(--color-background);
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(255, 0, 170, 0.18), transparent 28%),
|
||||
radial-gradient(circle at bottom right, rgba(130, 70, 255, 0.22), transparent 30%),
|
||||
linear-gradient(180deg, var(--color-background) 0%, var(--color-background-secondary) 100%)
|
||||
;
|
||||
color: white;
|
||||
}
|
||||
9
src/styles/elements/button.scss
Normal file
9
src/styles/elements/button.scss
Normal file
@@ -0,0 +1,9 @@
|
||||
button {
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: none;
|
||||
font-family: inherit;
|
||||
cursor: inherit;
|
||||
color: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
5
src/styles/elements/html.scss
Normal file
5
src/styles/elements/html.scss
Normal file
@@ -0,0 +1,5 @@
|
||||
html {
|
||||
height: 100%;
|
||||
max-width: 100vw;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
31
src/styles/elements/root.scss
Normal file
31
src/styles/elements/root.scss
Normal file
@@ -0,0 +1,31 @@
|
||||
:root {
|
||||
--color-background: #12091f;
|
||||
--color-background-secondary: #0e0718;
|
||||
|
||||
--color-surface: rgba(26, 15, 44, 1);
|
||||
|
||||
--color-neumorph-light: rgba(255, 255, 255, 0.1);
|
||||
--color-neumorph-dark: rgba(0, 0, 0, 0.25);
|
||||
|
||||
--radius-large: 1.75rem;
|
||||
|
||||
--box-shadow-raised-high:
|
||||
10px 10px 24px rgba(5,2,10,0.85),
|
||||
-8px -8px 20px rgba(91,53,141,0.08),
|
||||
inset 1px 1px 0 rgba(255,255,255,0.03)
|
||||
;
|
||||
|
||||
--box-shadow-raised-low:
|
||||
6px 6px 14px rgba(8,3,15,0.9),
|
||||
-5px -5px 14px rgba(103,64,156,0.1)
|
||||
;
|
||||
--box-shadow-lowered-low:
|
||||
inset 3px 3px 8px rgba(6,2,12,0.95),
|
||||
inset -2px -2px 8px rgba(118,76,176,0.08)
|
||||
;
|
||||
|
||||
--backdrop-raised-high: blur(12px);
|
||||
--backdrop-raised-low: blur(6px);
|
||||
|
||||
--max-width-large: 1600px;
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import { Request } from "express";
|
||||
import { Page } from "./page";
|
||||
import { LocaleLanguage } from "./locale";
|
||||
import { Section, SectionRenderer, SectionType, SectionTypeFor } from "./section";
|
||||
|
||||
export type TemplateSections = {
|
||||
[key in SectionType]?:SectionRenderer<key>;
|
||||
};
|
||||
|
||||
export type Template = {
|
||||
name:string;
|
||||
sections:TemplateSections;
|
||||
render:(p:{
|
||||
page:Page;
|
||||
request:Request;
|
||||
language:LocaleLanguage;
|
||||
}) => Promise<string>;
|
||||
}
|
||||
|
||||
export const templateRender = (p:{
|
||||
page:Page,
|
||||
template:Template,
|
||||
request:Request,
|
||||
language:LocaleLanguage
|
||||
}):Promise<string> => {
|
||||
return p.template.render(p);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { SectionRenderer } from "../../section";
|
||||
|
||||
const DefaultTemplateHeroSection:SectionRenderer<'hero'> = async ({
|
||||
properties,
|
||||
template,
|
||||
language,
|
||||
request
|
||||
}) => {
|
||||
return [
|
||||
'<div>',
|
||||
`<h1>${properties.title}</h1>`,
|
||||
'</div>'
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
export default DefaultTemplateHeroSection;
|
||||
@@ -1,42 +0,0 @@
|
||||
import { sectionRender } from "../../section";
|
||||
import { Template } from "../../template";
|
||||
|
||||
import DefaultTemplateHeroSection from "./hero";
|
||||
|
||||
const TEMPLATE_DEFAULT:Template = {
|
||||
name: 'default',
|
||||
sections: {
|
||||
'hero': DefaultTemplateHeroSection
|
||||
},
|
||||
render: async ({ language, page, request }) => {
|
||||
return [
|
||||
`<!DOCTYPE html>`,
|
||||
`<html>`,
|
||||
`<head>`,
|
||||
`<meta charset="UTF-8" />`,
|
||||
`<title>${page.title ? page.title[language] : 'Untitled Page'}</title>`,
|
||||
`<style type="text/css">`,
|
||||
`body { font-family: Arial, sans-serif; margin: 0; padding: 0; }`,
|
||||
`h1 { color: #333; }`,
|
||||
`</style>`,
|
||||
`</head>`,
|
||||
|
||||
`<body>`,
|
||||
`header`,
|
||||
...(await Promise.all(page.sections.map(async section => {
|
||||
return await sectionRender({
|
||||
language,
|
||||
page,
|
||||
request,
|
||||
template: TEMPLATE_DEFAULT,
|
||||
section
|
||||
});
|
||||
}))),
|
||||
`footer`,
|
||||
`</body>`,
|
||||
`</html>`
|
||||
].join('\n');
|
||||
}
|
||||
};
|
||||
|
||||
export default TEMPLATE_DEFAULT;
|
||||
@@ -1,16 +0,0 @@
|
||||
import { SectionRenderer } from "../../section";
|
||||
|
||||
const PSPTemplateHeroSection:SectionRenderer<'hero'> = async ({
|
||||
properties,
|
||||
template,
|
||||
language,
|
||||
request
|
||||
}) => {
|
||||
return [
|
||||
'<div>',
|
||||
`<h1>${properties.title}</h1>`,
|
||||
'</div>'
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
export default PSPTemplateHeroSection;
|
||||
@@ -1,42 +0,0 @@
|
||||
import { sectionRender } from "../../section";
|
||||
import { Template } from "../../template";
|
||||
|
||||
import PSPTemplateHeroSection from "./hero";
|
||||
|
||||
const TEMPLATE_PSP:Template = {
|
||||
name: 'psp',
|
||||
sections: {
|
||||
'hero': PSPTemplateHeroSection
|
||||
},
|
||||
render: async ({ language, page, request }) => {
|
||||
return [
|
||||
`<!DOCTYPE html>`,
|
||||
`<html>`,
|
||||
`<head>`,
|
||||
`<meta charset="UTF-8" />`,
|
||||
`<title>${page.title ? page.title[language] : 'Untitled Page'}</title>`,
|
||||
`<style type="text/css">`,
|
||||
`body { font-family: Arial, sans-serif; margin: 0; padding: 0; }`,
|
||||
`h1 { color: #333; }`,
|
||||
`</style>`,
|
||||
`</head>`,
|
||||
|
||||
`<body>`,
|
||||
`header`,
|
||||
...(await Promise.all(page.sections.map(async section => {
|
||||
return await sectionRender({
|
||||
language,
|
||||
page,
|
||||
request,
|
||||
template: TEMPLATE_PSP,
|
||||
section
|
||||
});
|
||||
}))),
|
||||
`footer`,
|
||||
`</body>`,
|
||||
`</html>`
|
||||
].join('\n');
|
||||
}
|
||||
};
|
||||
|
||||
export default TEMPLATE_PSP;
|
||||
@@ -1,14 +1,34 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "commonjs",
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"target": "ES2017",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react-jsx",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
".next/dev/types/**/*.ts",
|
||||
"**/*.mts"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
145
types/cache-life.d.ts
vendored
Normal file
145
types/cache-life.d.ts
vendored
Normal file
@@ -0,0 +1,145 @@
|
||||
// Type definitions for Next.js cacheLife configs
|
||||
|
||||
declare module 'next/cache' {
|
||||
export { unstable_cache } from 'next/dist/server/web/spec-extension/unstable-cache'
|
||||
export {
|
||||
updateTag,
|
||||
revalidateTag,
|
||||
revalidatePath,
|
||||
refresh,
|
||||
} from 'next/dist/server/web/spec-extension/revalidate'
|
||||
export { unstable_noStore } from 'next/dist/server/web/spec-extension/unstable-no-store'
|
||||
|
||||
|
||||
/**
|
||||
* Cache this `"use cache"` for a timespan defined by the `"default"` profile.
|
||||
* ```
|
||||
* stale: 300 seconds (5 minutes)
|
||||
* revalidate: 900 seconds (15 minutes)
|
||||
* expire: never
|
||||
* ```
|
||||
*
|
||||
* This cache may be stale on clients for 5 minutes before checking with the server.
|
||||
* If the server receives a new request after 15 minutes, start revalidating new values in the background.
|
||||
* It lives for the maximum age of the server cache. If this entry has no traffic for a while, it may serve an old value the next request.
|
||||
*/
|
||||
export function cacheLife(profile: "default"): void
|
||||
|
||||
/**
|
||||
* Cache this `"use cache"` for a timespan defined by the `"seconds"` profile.
|
||||
* ```
|
||||
* stale: 30 seconds
|
||||
* revalidate: 1 seconds
|
||||
* expire: 60 seconds (1 minute)
|
||||
* ```
|
||||
*
|
||||
* This cache may be stale on clients for 30 seconds before checking with the server.
|
||||
* If the server receives a new request after 1 seconds, start revalidating new values in the background.
|
||||
* If this entry has no traffic for 1 minute it will expire. The next request will recompute it.
|
||||
*/
|
||||
export function cacheLife(profile: "seconds"): void
|
||||
|
||||
/**
|
||||
* Cache this `"use cache"` for a timespan defined by the `"minutes"` profile.
|
||||
* ```
|
||||
* stale: 300 seconds (5 minutes)
|
||||
* revalidate: 60 seconds (1 minute)
|
||||
* expire: 3600 seconds (1 hour)
|
||||
* ```
|
||||
*
|
||||
* This cache may be stale on clients for 5 minutes before checking with the server.
|
||||
* If the server receives a new request after 1 minute, start revalidating new values in the background.
|
||||
* If this entry has no traffic for 1 hour it will expire. The next request will recompute it.
|
||||
*/
|
||||
export function cacheLife(profile: "minutes"): void
|
||||
|
||||
/**
|
||||
* Cache this `"use cache"` for a timespan defined by the `"hours"` profile.
|
||||
* ```
|
||||
* stale: 300 seconds (5 minutes)
|
||||
* revalidate: 3600 seconds (1 hour)
|
||||
* expire: 86400 seconds (1 day)
|
||||
* ```
|
||||
*
|
||||
* This cache may be stale on clients for 5 minutes before checking with the server.
|
||||
* If the server receives a new request after 1 hour, start revalidating new values in the background.
|
||||
* If this entry has no traffic for 1 day it will expire. The next request will recompute it.
|
||||
*/
|
||||
export function cacheLife(profile: "hours"): void
|
||||
|
||||
/**
|
||||
* Cache this `"use cache"` for a timespan defined by the `"days"` profile.
|
||||
* ```
|
||||
* stale: 300 seconds (5 minutes)
|
||||
* revalidate: 86400 seconds (1 day)
|
||||
* expire: 604800 seconds (1 week)
|
||||
* ```
|
||||
*
|
||||
* This cache may be stale on clients for 5 minutes before checking with the server.
|
||||
* If the server receives a new request after 1 day, start revalidating new values in the background.
|
||||
* If this entry has no traffic for 1 week it will expire. The next request will recompute it.
|
||||
*/
|
||||
export function cacheLife(profile: "days"): void
|
||||
|
||||
/**
|
||||
* Cache this `"use cache"` for a timespan defined by the `"weeks"` profile.
|
||||
* ```
|
||||
* stale: 300 seconds (5 minutes)
|
||||
* revalidate: 604800 seconds (1 week)
|
||||
* expire: 2592000 seconds (1 month)
|
||||
* ```
|
||||
*
|
||||
* This cache may be stale on clients for 5 minutes before checking with the server.
|
||||
* If the server receives a new request after 1 week, start revalidating new values in the background.
|
||||
* If this entry has no traffic for 1 month it will expire. The next request will recompute it.
|
||||
*/
|
||||
export function cacheLife(profile: "weeks"): void
|
||||
|
||||
/**
|
||||
* Cache this `"use cache"` for a timespan defined by the `"max"` profile.
|
||||
* ```
|
||||
* stale: 300 seconds (5 minutes)
|
||||
* revalidate: 2592000 seconds (1 month)
|
||||
* expire: 31536000 seconds (365 days)
|
||||
* ```
|
||||
*
|
||||
* This cache may be stale on clients for 5 minutes before checking with the server.
|
||||
* If the server receives a new request after 1 month, start revalidating new values in the background.
|
||||
* If this entry has no traffic for 365 days it will expire. The next request will recompute it.
|
||||
*/
|
||||
export function cacheLife(profile: "max"): void
|
||||
|
||||
/**
|
||||
* Cache this `"use cache"` using a custom timespan.
|
||||
* ```
|
||||
* stale: ... // seconds
|
||||
* revalidate: ... // seconds
|
||||
* expire: ... // seconds
|
||||
* ```
|
||||
*
|
||||
* This is similar to Cache-Control: max-age=`stale`,s-max-age=`revalidate`,stale-while-revalidate=`expire-revalidate`
|
||||
*
|
||||
* If a value is left out, the lowest of other cacheLife() calls or the default, is used instead.
|
||||
*/
|
||||
export function cacheLife(profile: {
|
||||
/**
|
||||
* This cache may be stale on clients for ... seconds before checking with the server.
|
||||
*/
|
||||
stale?: number,
|
||||
/**
|
||||
* If the server receives a new request after ... seconds, start revalidating new values in the background.
|
||||
*/
|
||||
revalidate?: number,
|
||||
/**
|
||||
* If this entry has no traffic for ... seconds it will expire. The next request will recompute it.
|
||||
*/
|
||||
expire?: number
|
||||
}): void
|
||||
|
||||
|
||||
import { cacheTag } from 'next/dist/server/use-cache/cache-tag'
|
||||
export { cacheTag }
|
||||
|
||||
export const unstable_cacheTag: typeof cacheTag
|
||||
export const unstable_cacheLife: typeof cacheLife
|
||||
}
|
||||
57
types/routes.d.ts
vendored
Normal file
57
types/routes.d.ts
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
// This file is generated automatically by Next.js
|
||||
// Do not edit this file manually
|
||||
|
||||
type AppRoutes = "/"
|
||||
type PageRoutes = never
|
||||
type LayoutRoutes = "/"
|
||||
type RedirectRoutes = never
|
||||
type RewriteRoutes = never
|
||||
type Routes = AppRoutes | PageRoutes | LayoutRoutes | RedirectRoutes | RewriteRoutes
|
||||
|
||||
|
||||
interface ParamMap {
|
||||
"/": {}
|
||||
}
|
||||
|
||||
|
||||
export type ParamsOf<Route extends Routes> = ParamMap[Route]
|
||||
|
||||
interface LayoutSlotMap {
|
||||
"/": never
|
||||
}
|
||||
|
||||
|
||||
export type { AppRoutes, PageRoutes, LayoutRoutes, RedirectRoutes, RewriteRoutes, ParamMap }
|
||||
|
||||
declare global {
|
||||
/**
|
||||
* Props for Next.js App Router page components
|
||||
* @example
|
||||
* ```tsx
|
||||
* export default function Page(props: PageProps<'/blog/[slug]'>) {
|
||||
* const { slug } = await props.params
|
||||
* return <div>Blog post: {slug}</div>
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
interface PageProps<AppRoute extends AppRoutes> {
|
||||
params: Promise<ParamMap[AppRoute]>
|
||||
searchParams: Promise<Record<string, string | string[] | undefined>>
|
||||
}
|
||||
|
||||
/**
|
||||
* Props for Next.js App Router layout components
|
||||
* @example
|
||||
* ```tsx
|
||||
* export default function Layout(props: LayoutProps<'/dashboard'>) {
|
||||
* return <div>{props.children}</div>
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
type LayoutProps<LayoutRoute extends LayoutRoutes> = {
|
||||
params: Promise<ParamMap[LayoutRoute]>
|
||||
children: React.ReactNode
|
||||
} & {
|
||||
[K in LayoutSlotMap[LayoutRoute]]: React.ReactNode
|
||||
}
|
||||
}
|
||||
61
types/validator.ts
Normal file
61
types/validator.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
// This file is generated automatically by Next.js
|
||||
// Do not edit this file manually
|
||||
// This file validates that all pages and layouts export the correct types
|
||||
|
||||
import type { AppRoutes, LayoutRoutes, ParamMap } from "./routes.js"
|
||||
import type { ResolvingMetadata, ResolvingViewport } from "next/types.js"
|
||||
|
||||
type AppPageConfig<Route extends AppRoutes = AppRoutes> = {
|
||||
default: React.ComponentType<{ params: Promise<ParamMap[Route]> } & any> | ((props: { params: Promise<ParamMap[Route]> } & any) => React.ReactNode | Promise<React.ReactNode> | never | void | Promise<void>)
|
||||
generateStaticParams?: (props: { params: ParamMap[Route] }) => Promise<any[]> | any[]
|
||||
generateMetadata?: (
|
||||
props: { params: Promise<ParamMap[Route]> } & any,
|
||||
parent: ResolvingMetadata
|
||||
) => Promise<any> | any
|
||||
generateViewport?: (
|
||||
props: { params: Promise<ParamMap[Route]> } & any,
|
||||
parent: ResolvingViewport
|
||||
) => Promise<any> | any
|
||||
metadata?: any
|
||||
viewport?: any
|
||||
}
|
||||
|
||||
type LayoutConfig<Route extends LayoutRoutes = LayoutRoutes> = {
|
||||
default: React.ComponentType<LayoutProps<Route>> | ((props: LayoutProps<Route>) => React.ReactNode | Promise<React.ReactNode> | never | void | Promise<void>)
|
||||
generateStaticParams?: (props: { params: ParamMap[Route] }) => Promise<any[]> | any[]
|
||||
generateMetadata?: (
|
||||
props: { params: Promise<ParamMap[Route]> } & any,
|
||||
parent: ResolvingMetadata
|
||||
) => Promise<any> | any
|
||||
generateViewport?: (
|
||||
props: { params: Promise<ParamMap[Route]> } & any,
|
||||
parent: ResolvingViewport
|
||||
) => Promise<any> | any
|
||||
metadata?: any
|
||||
viewport?: any
|
||||
}
|
||||
|
||||
|
||||
// Validate ../../src/app/page.tsx
|
||||
{
|
||||
type __IsExpected<Specific extends AppPageConfig<"/">> = Specific
|
||||
const handler = {} as typeof import("../../src/app/page.js")
|
||||
type __Check = __IsExpected<typeof handler>
|
||||
// @ts-ignore
|
||||
type __Unused = __Check
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Validate ../../src/app/layout.tsx
|
||||
{
|
||||
type __IsExpected<Specific extends LayoutConfig<"/">> = Specific
|
||||
const handler = {} as typeof import("../../src/app/layout.js")
|
||||
type __Check = __IsExpected<typeof handler>
|
||||
// @ts-ignore
|
||||
type __Unused = __Check
|
||||
}
|
||||
Reference in New Issue
Block a user