Working on the lit evolution.
This commit is contained in:
parent
60f398b143
commit
9c97349ba7
|
@ -1,13 +1,115 @@
|
||||||
*.pyc
|
# Created by https://www.gitignore.io/api/node
|
||||||
*.pyo
|
# Edit at https://www.gitignore.io/?templates=node
|
||||||
*#
|
|
||||||
.#*
|
### Node ###
|
||||||
*~
|
# Logs
|
||||||
js/magnets.js
|
logs
|
||||||
js/sat.js
|
*.log
|
||||||
js/wordlist.js
|
npm-debug.log*
|
||||||
node_modules
|
yarn-debug.log*
|
||||||
server/magnet_server.js
|
yarn-error.log*
|
||||||
index.html
|
lerna-debug.log*
|
||||||
style.css
|
|
||||||
private/
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# TypeScript v1 declaration files
|
||||||
|
typings/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# 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
|
||||||
|
.env.test
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# next.js build output
|
||||||
|
.next
|
||||||
|
|
||||||
|
# nuxt.js build output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Uncomment the public line if your project uses Gatsby
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# https://create-react-app.dev/docs/using-the-public-folder/#docsNav
|
||||||
|
# public
|
||||||
|
|
||||||
|
# Storybook build outputs
|
||||||
|
.out
|
||||||
|
.storybook-out
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# Temporary folders
|
||||||
|
tmp/
|
||||||
|
temp/
|
||||||
|
|
||||||
|
# End of https://www.gitignore.io/api/node
|
||||||
|
api/**
|
||||||
|
storybook-static/
|
||||||
|
|
||||||
|
# Wireit's cache
|
||||||
|
.wireit
|
||||||
|
|
||||||
|
custom-elements.json
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2024 Elf M. Sternberg <elf.sternberg@gmail.com>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||||
|
associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||||
|
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||||
|
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||||
|
portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
||||||
|
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
||||||
|
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
113
README.md
113
README.md
|
@ -1,101 +1,20 @@
|
||||||
# Fridgemagnets - A Nifty HTML5 Toy With Some Twitterable Features!
|
This repository contains the boilerplate package which I use to start most of my smaller Typescript
|
||||||
|
projects. It is "primitive" in that it doesn't have hot module reloading or VSCode integration;
|
||||||
|
instead, it runs the build process using a watcher that enables fast iteration via the command line.
|
||||||
|
This boilerplate is focused on web components, primarily those build with [Lit](https://lit.dev).
|
||||||
|
|
||||||
## Demo
|
## Dependencies
|
||||||
|
|
||||||
Main website: [HTML5 Magnets!](http://html5magnets.elfsternberg.com)
|
Aside from the NodeJS dependencies, this script uses
|
||||||
|
[codespell](https://github.com/codespell-project/codespell), which is a Python-based spell checker
|
||||||
|
for comments and documentation.
|
||||||
|
|
||||||
Happy results: [@HTML5Magnets](https://twitter.com/#!/html5magnets)
|
## Decisions:
|
||||||
|
|
||||||
## Main Idea
|
|
||||||
|
|
||||||
Fridgemagnets is a straightforward simulation of a relaxing
|
|
||||||
refrigerator poem tileset. It was inspired by
|
|
||||||
[TwitterMagnets](http://twittermagnets.com/), a Flash app written by the
|
|
||||||
brilliant graphic designers at
|
|
||||||
[PlusGood](http://www.plusgood.co.uk/). I have a bit of Flash envy,
|
|
||||||
since I'm not an Adobe developer, and the TwitterMagnets application
|
|
||||||
bugged me. It didn't resize, it didn't do mobile very well, and
|
|
||||||
nobody has a space reserved on their fridge for the poem: a "poem" is
|
|
||||||
just a meaningful arrangement of words deliberately placed in close
|
|
||||||
proximity that seems to convey meaning.
|
|
||||||
|
|
||||||
Fridgemagnets has a lot of new and fun technologies: it uses the audio
|
|
||||||
API, it involves all manner of write-only-DOM tricks to make resizing
|
|
||||||
work well, and it's my first major piece of express.js software. (I
|
|
||||||
originally thought of using Zappa, but decided against it; dispatch is
|
|
||||||
not the biggest thing Node has to deal with, and express by itself
|
|
||||||
works just fine in Coffee.)
|
|
||||||
|
|
||||||
I can now add the Twitter API, the HTML5 Audio API, and some basic
|
|
||||||
game mechanics ([Separate Axis
|
|
||||||
Theorem](http://www.metanetsoftware.com/technique/tutorialA.html) for
|
|
||||||
collision management, anyone?) to my resume.
|
|
||||||
|
|
||||||
This is known to work in later versions of Chrome, Firefox, and IE8+
|
|
||||||
under Windows XP. No promise is implied of it working on your version
|
|
||||||
of those, or any other browser. It's not (yet) phone-ready.
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
Node.js. Most of the subsidiary requirements can be found in the two
|
|
||||||
package.json files. For development purposes Coffeescript, LessCSS,
|
|
||||||
and HAML are in heavy use.
|
|
||||||
|
|
||||||
If you're running the server, you need MySQL. The schema for the
|
|
||||||
MySQL database can be found in the server folder.
|
|
||||||
|
|
||||||
A config file. There's an example in the server folder.
|
|
||||||
|
|
||||||
A twitter developer's account. Get one at dev.twitter.com.
|
|
||||||
|
|
||||||
If you're going to be using the test/deploy routine, inotify-tools and
|
|
||||||
python's "fabric" program are very useful.
|
|
||||||
|
|
||||||
If you're going to make this publicly available, I strongly recommend
|
|
||||||
you run this as its own user in a low-permissions container, behind
|
|
||||||
Nginx and a lot of smarts. Also, the Node.js program "forever" is
|
|
||||||
very useful in keeping the server up.
|
|
||||||
|
|
||||||
## Acknowledgements
|
|
||||||
|
|
||||||
[PlusGood](http://www.plusgood.co.uk/), for the inspiration.
|
|
||||||
|
|
||||||
[Emily Richards aka Snowflake](http://ccmixter.org/people/snowflake),
|
|
||||||
for her beautiful music.
|
|
||||||
|
|
||||||
The entire crew at [Nodejitsu](http://nodejitsu.com/), for all the
|
|
||||||
encouragement, even if I don't use their services.
|
|
||||||
|
|
||||||
## CREDITS
|
|
||||||
|
|
||||||
"Ethereal Space" is copyright (c) 2011 Snowflake, licensed under a
|
|
||||||
Creative Commons 3.0 Attribution-Required license.
|
|
||||||
|
|
||||||
jQuery, jQuery UI and associated assets, Buzz.js, and jQuery CSS
|
|
||||||
Transform are copyright their respective owners, and available under
|
|
||||||
a permissive MIT license.
|
|
||||||
|
|
||||||
## LICENSE AND COPYRIGHT NOTICE: NO WARRANTY GRANTED OR IMPLIED
|
|
||||||
|
|
||||||
Copyright (c) 2012 Elf M. Sternberg
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
|
|
||||||
- Elf M. Sternberg <elf@pendorwright.com>
|
|
||||||
|
|
||||||
|
- TSC is still the best analyzer of Typescript's types.
|
||||||
|
- Scripting ESBuild gives you enormous power.
|
||||||
|
- Many of the commands have the `${NODE_RUNNER}` prefix. If you set `export NODE_RUNNER=bun`, you
|
||||||
|
can get a huge speedup in building and linting.
|
||||||
|
- Knip makes sure you're not importing anything you're not using.
|
||||||
|
- As this is a Lit-focused project, both Lit-Analyzer and WC-Analyzer are included
|
||||||
|
- xo
|
||||||
|
|
|
@ -0,0 +1,133 @@
|
||||||
|
import { execFileSync } from "child_process";
|
||||||
|
import * as chokidar from "chokidar";
|
||||||
|
import esbuild from "esbuild";
|
||||||
|
import fs from "fs";
|
||||||
|
import { globSync } from "glob";
|
||||||
|
import path from "path";
|
||||||
|
import { cwd } from "process";
|
||||||
|
import process from "process";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
|
// Hack replaces what was lost when using Bun / later Node versions
|
||||||
|
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
||||||
|
|
||||||
|
const isProdBuild = process.env.NODE_ENV === "production";
|
||||||
|
|
||||||
|
const definitions = {
|
||||||
|
"process.env.NODE_ENV": JSON.stringify(isProdBuild ? "production" : "development"),
|
||||||
|
"process.env.CWD": JSON.stringify(cwd()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// If you have assets in your src folder that won't be built/bundled, put them into "otherFiles" to
|
||||||
|
// copy them. All this is a replacement for rollup-copy-plugin, which I used to use.
|
||||||
|
|
||||||
|
const otherFiles = [
|
||||||
|
["./src/*.css", "."],
|
||||||
|
["./src/*.png", "."]
|
||||||
|
];
|
||||||
|
|
||||||
|
const isFile = (filePath) => fs.statSync(filePath).isFile();
|
||||||
|
function nameCopyTarget(src, dest, strip) {
|
||||||
|
const target = path.join(dest, strip ? src.replace(strip, "") : path.parse(src).base);
|
||||||
|
return [src, target];
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyOthers() {
|
||||||
|
for (const [source, rawdest, strip] of otherFiles) {
|
||||||
|
const matchedPaths = globSync(source);
|
||||||
|
const dest = path.join("dist", rawdest);
|
||||||
|
const copyTargets = matchedPaths.map((path) => nameCopyTarget(path, dest, strip));
|
||||||
|
for (const [src, dest] of copyTargets) {
|
||||||
|
if (isFile(src)) {
|
||||||
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
||||||
|
fs.copyFileSync(src, dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This starts the definitions used for esbuild: Targets, arguments, the function for running a
|
||||||
|
// build, and the options for building: building or watching. If you're building more than one app,
|
||||||
|
// order them by the approximately largest project to smallest to build even faster.
|
||||||
|
|
||||||
|
const apps = [
|
||||||
|
["index.ts", "."]
|
||||||
|
];
|
||||||
|
|
||||||
|
const baseArgs = {
|
||||||
|
bundle: true,
|
||||||
|
write: true,
|
||||||
|
sourcemap: true,
|
||||||
|
minify: isProdBuild,
|
||||||
|
splitting: true,
|
||||||
|
treeShaking: true,
|
||||||
|
external: ["*.woff", "*.woff2"],
|
||||||
|
tsconfig: "./tsconfig.json",
|
||||||
|
loader: { ".css": "text", ".md": "text" },
|
||||||
|
define: definitions,
|
||||||
|
format: "esm",
|
||||||
|
};
|
||||||
|
|
||||||
|
async function buildOneSource(source, dest) {
|
||||||
|
const DIST = path.join(__dirname, "./dist", dest);
|
||||||
|
console.log(`[${new Date(Date.now()).toISOString()}] Starting build for target ${source}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const start = Date.now();
|
||||||
|
copyOthers();
|
||||||
|
await esbuild.build({
|
||||||
|
...baseArgs,
|
||||||
|
entryPoints: [`./src/${source}`],
|
||||||
|
entryNames: '[dir]/[name]',
|
||||||
|
outdir: DIST,
|
||||||
|
});
|
||||||
|
const end = Date.now();
|
||||||
|
console.log(
|
||||||
|
`[${new Date(end).toISOString()}] Finished build for target ${source} in ${
|
||||||
|
Date.now() - start
|
||||||
|
}ms`,
|
||||||
|
);
|
||||||
|
} catch (exc) {
|
||||||
|
console.error(`[${new Date(Date.now()).toISOString()}] Failed to build ${source}: ${exc}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function buildAll(apps) {
|
||||||
|
await Promise.allSettled(apps.map(([source, dest]) => buildOneSource(source, dest)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let timeoutId = null;
|
||||||
|
function debouncedBuild() {
|
||||||
|
if (timeoutId !== null) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
console.log("\x1bc");
|
||||||
|
buildAll(apps);
|
||||||
|
}, 250);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.argv.length > 2 && (process.argv[2] === "-h" || process.argv[2] === "--help")) {
|
||||||
|
console.log(`Build:
|
||||||
|
|
||||||
|
options:
|
||||||
|
-w, --watch: Build all ${apps.length} applications
|
||||||
|
-h, --help: This help message
|
||||||
|
`);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.argv.length > 2 && (process.argv[2] === "-w" || process.argv[2] === "--watch")) {
|
||||||
|
console.log("Watching ./src for changes");
|
||||||
|
chokidar.watch("./src").on("all", (event, path) => {
|
||||||
|
if (!["add", "change", "unlink"].includes(event)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!/(\.css|\.ts|\.js)$/.test(path)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
debouncedBuild();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await buildAll(apps);
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
import eslint from "@eslint/js";
|
||||||
|
import tsparser from "@typescript-eslint/parser";
|
||||||
|
import litconf from "eslint-plugin-lit";
|
||||||
|
import wcconf from "eslint-plugin-wc";
|
||||||
|
import globals from "globals";
|
||||||
|
import tseslint from "typescript-eslint";
|
||||||
|
|
||||||
|
export default [
|
||||||
|
// You would not believe how much this change has frustrated users: ["if an ignores key is used
|
||||||
|
// without any other keys in the configuration object, then the patterns act as global
|
||||||
|
// ignores"](https://eslint.org/docs/latest/use/configure/ignore)
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
"dist/",
|
||||||
|
// don't lint the cache
|
||||||
|
".wireit/",
|
||||||
|
// let packages have their own configurations
|
||||||
|
"packages/",
|
||||||
|
// don't ever lint node_modules
|
||||||
|
"node_modules/",
|
||||||
|
".storybook/*",
|
||||||
|
// don't lint build output (make sure it's set to your correct build folder name)
|
||||||
|
// don't lint nyc coverage output
|
||||||
|
"coverage/",
|
||||||
|
"src/locale-codes.ts",
|
||||||
|
"storybook-static/",
|
||||||
|
"src/locales/",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
eslint.configs.recommended,
|
||||||
|
wcconf.configs["flat/recommended"],
|
||||||
|
litconf.configs["flat/recommended"],
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
parser: tsparser,
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 12,
|
||||||
|
sourceType: "module",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
files: ["src/**"],
|
||||||
|
rules: {
|
||||||
|
"no-unused-vars": "off",
|
||||||
|
"no-console": ["error", { allow: ["debug", "warn", "error"] }],
|
||||||
|
"@typescript-eslint/ban-ts-comment": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
argsIgnorePattern: "^_",
|
||||||
|
varsIgnorePattern: "^_",
|
||||||
|
caughtErrorsIgnorePattern: "^_",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
parser: tsparser,
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 12,
|
||||||
|
sourceType: "module",
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
...globals.nodeBuiltin,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
files: ["scripts/*.mjs", "*.ts", "*.mjs"],
|
||||||
|
rules: {
|
||||||
|
"no-unused-vars": "off",
|
||||||
|
// We WANT our scripts to output to the console!
|
||||||
|
"no-console": "off",
|
||||||
|
"@typescript-eslint/ban-ts-comment": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
argsIgnorePattern: "^_",
|
||||||
|
varsIgnorePattern: "^_",
|
||||||
|
caughtErrorsIgnorePattern: "^_",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,15 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en-GB">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400..900&display=swap" rel="stylesheet" />
|
||||||
|
<script src="./dist/index.js" type="text/javascript"></script>
|
||||||
|
<link href="./dist/styles.css" rel="stylesheet" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<fridge-magnets id="fridgemagnets">
|
||||||
|
</fridge-magnets>
|
||||||
|
</body>
|
||||||
|
</html>
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,168 @@
|
||||||
|
{
|
||||||
|
"name": "elf-boilerplate",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@lit/localize": "^0.12.2",
|
||||||
|
"@neodrag/vanilla": "^2.0.5",
|
||||||
|
"lit": "^3.2.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/eslint__js": "^8.42.3",
|
||||||
|
"@types/node": "^22.5.5",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^8.6.0",
|
||||||
|
"@typescript-eslint/parser": "^8.6.0",
|
||||||
|
"chokidar": "^4.0.1",
|
||||||
|
"esbuild": "^0.24.0",
|
||||||
|
"eslint": "^9.11.0",
|
||||||
|
"eslint-plugin-lit": "^1.15.0",
|
||||||
|
"eslint-plugin-wc": "^2.1.1",
|
||||||
|
"glob": "^10.4.5",
|
||||||
|
"http-server": "^14.1.1",
|
||||||
|
"knip": "^5.30.4",
|
||||||
|
"lit-analyzer": "^2.0.3",
|
||||||
|
"lockfile-lint": "^4.14.0",
|
||||||
|
"prettier": "^3.3.3",
|
||||||
|
"rimraf": "^5.0.10",
|
||||||
|
"syncpack": "^13.0.0",
|
||||||
|
"typescript": "^5.6.2",
|
||||||
|
"typescript-eslint": "^8.6.0",
|
||||||
|
"wireit": "^0.14.9"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@esbuild/darwin-arm64": "^0.23.0",
|
||||||
|
"@esbuild/linux-amd64": "^0.18.11",
|
||||||
|
"@esbuild/linux-arm64": "^0.23.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "wireit",
|
||||||
|
"clean": "wireit",
|
||||||
|
"demo": "wireit",
|
||||||
|
"fix": "wireit",
|
||||||
|
"format": "wireit",
|
||||||
|
"lint": "wireit",
|
||||||
|
"realclean": "wireit",
|
||||||
|
"watch": "wireit"
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"wireit": {
|
||||||
|
"build": {
|
||||||
|
"command": "${NODE_RUNNER} build.mjs",
|
||||||
|
"files": [
|
||||||
|
"./src/**/*.{css,ts,js}"
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
"dist/**"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"NODE_RUNNER": {
|
||||||
|
"external": true,
|
||||||
|
"default": "node"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"clean": {
|
||||||
|
"command": "rimraf ./dist"
|
||||||
|
},
|
||||||
|
"demo": {
|
||||||
|
"command": "${NODE_RUNNER} ./node_modules/.bin/http-server . --port ${HTTP_DEMO_PORT}",
|
||||||
|
"server": true,
|
||||||
|
"env": {
|
||||||
|
"NODE_RUNNER": {
|
||||||
|
"external": true,
|
||||||
|
"default": "node"
|
||||||
|
},
|
||||||
|
"HTTP_DEMO_PORT": {
|
||||||
|
"external": true,
|
||||||
|
"default": "8000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fix": {
|
||||||
|
"command": "prettier --write .",
|
||||||
|
"dependencies": [
|
||||||
|
"fix:eslint",
|
||||||
|
"fix:package"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"format": {
|
||||||
|
"command": "prettier --write ."
|
||||||
|
},
|
||||||
|
"fix:eslint": {
|
||||||
|
"command": "${NODE_RUNNER ./scripts/eslint.mjs --fix",
|
||||||
|
"env": {
|
||||||
|
"NODE_RUNNER": {
|
||||||
|
"external": true,
|
||||||
|
"default": "node"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint:components": {
|
||||||
|
"command": "lit-analyzer src"
|
||||||
|
},
|
||||||
|
"lint:eslint": {
|
||||||
|
"command": "${NODE_RUNNER} ./scripts/eslint.mjs --precommit",
|
||||||
|
"env": {
|
||||||
|
"NODE_RUNNER": {
|
||||||
|
"external": true,
|
||||||
|
"default": "node"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint:lockfile": {
|
||||||
|
"_comment": "Ensure every entry has a resolved hash",
|
||||||
|
"shell": true,
|
||||||
|
"command": "[ -z \"$(jq -r '.packages | to_entries[] | select((.key | startswith(\"node_modules\")) and (.value | has(\"resolved\") | not)) | .key' < package-lock.json)\" ]",
|
||||||
|
"dependencies": [
|
||||||
|
"lint:lockfile:base"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"lint:lockfile:base": {
|
||||||
|
"command": "lockfile-lint --path ./package-lock.json --allowed-hosts npm --validate-https --validate-integrity"
|
||||||
|
},
|
||||||
|
"lint:spelling": {
|
||||||
|
"command": "${NODE_RUNNER} scripts/check-spelling.mjs",
|
||||||
|
"env": {
|
||||||
|
"NODE_RUNNER": {
|
||||||
|
"external": true,
|
||||||
|
"default": "node"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"prettier": {
|
||||||
|
"command": "prettier ."
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"dependencies": [
|
||||||
|
"lint:types",
|
||||||
|
"lint:components",
|
||||||
|
"lint:lit",
|
||||||
|
"lint:lockfile",
|
||||||
|
"lint:eslint",
|
||||||
|
"prettier"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"lint:imports": {
|
||||||
|
"command": "knip --config scripts/knip.config.ts"
|
||||||
|
},
|
||||||
|
"lint:lit": {
|
||||||
|
"command": "lit-analyzer src"
|
||||||
|
},
|
||||||
|
"lint:types": {
|
||||||
|
"command": "tsc --noEmit -p ."
|
||||||
|
},
|
||||||
|
"watch": {
|
||||||
|
"command": "${NODE_RUNNER} build.mjs --watch",
|
||||||
|
"server": true,
|
||||||
|
"env": {
|
||||||
|
"NODE_RUNNER": {
|
||||||
|
"external": true,
|
||||||
|
"default": "node"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
import { execFileSync } from "child_process";
|
||||||
|
import { ESLint } from "eslint";
|
||||||
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
import process from "process";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
|
function changedFiles() {
|
||||||
|
const gitStatus = execFileSync("git", ["diff", "--name-only", "HEAD"], { encoding: "utf8" });
|
||||||
|
const gitUntracked = execFileSync("git", ["ls-files", "--others", "--exclude-standard"], {
|
||||||
|
encoding: "utf8",
|
||||||
|
});
|
||||||
|
|
||||||
|
const changed = gitStatus
|
||||||
|
.split("\n")
|
||||||
|
.filter((line) => line.trim().substring(0, 4) === "web/")
|
||||||
|
.filter((line) => /\.(m|c)?(t|j)s$/.test(line))
|
||||||
|
.map((line) => line.substring(4))
|
||||||
|
.filter((line) => fs.existsSync(line));
|
||||||
|
|
||||||
|
const untracked = gitUntracked
|
||||||
|
.split("\n")
|
||||||
|
.filter((line) => /\.(m|c)?(t|j)s$/.test(line))
|
||||||
|
.filter((line) => fs.existsSync(line));
|
||||||
|
|
||||||
|
const sourceFiles = [...changed, ...untracked].filter((line) => /^src\//.test(line));
|
||||||
|
const scriptFiles = [...changed, ...untracked].filter(
|
||||||
|
(line) => /^scripts\//.test(line) || !/^src\//.test(line),
|
||||||
|
);
|
||||||
|
|
||||||
|
return [...sourceFiles, ...scriptFiles];
|
||||||
|
}
|
||||||
|
|
||||||
|
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
||||||
|
const projectRoot = path.join(__dirname, "..");
|
||||||
|
process.chdir(projectRoot);
|
||||||
|
|
||||||
|
const hasFlag = (flags) => process.argv.length > 1 && flags.includes(process.argv[2]);
|
||||||
|
|
||||||
|
const [configFile, files] = hasFlag(["-n", "--nightmare"])
|
||||||
|
? [path.join(__dirname, "eslint.nightmare.mjs"), changedFiles()]
|
||||||
|
: hasFlag(["-p", "--precommit"])
|
||||||
|
? [path.join(__dirname, "eslint.precommit.mjs"), changedFiles()]
|
||||||
|
: [path.join(projectRoot, "eslint.config.mjs"), ["."]];
|
||||||
|
|
||||||
|
const eslint = new ESLint({
|
||||||
|
overrideConfigFile: configFile,
|
||||||
|
warnIgnored: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const results = await eslint.lintFiles(files);
|
||||||
|
const formatter = await eslint.loadFormatter("stylish");
|
||||||
|
const resultText = formatter.format(results);
|
||||||
|
const errors = results.reduce((acc, result) => acc + result.errorCount, 0);
|
||||||
|
console.log(resultText);
|
||||||
|
process.exit(errors > 1 ? 1 : 0);
|
|
@ -0,0 +1,199 @@
|
||||||
|
import eslint from "@eslint/js";
|
||||||
|
import tsparser from "@typescript-eslint/parser";
|
||||||
|
import litconf from "eslint-plugin-lit";
|
||||||
|
import wcconf from "eslint-plugin-wc";
|
||||||
|
import globals from "globals";
|
||||||
|
import tseslint from "typescript-eslint";
|
||||||
|
|
||||||
|
const MAX_DEPTH = 4;
|
||||||
|
const MAX_NESTED_CALLBACKS = 4;
|
||||||
|
const MAX_PARAMS = 5;
|
||||||
|
|
||||||
|
const rules = {
|
||||||
|
"accessor-pairs": "error",
|
||||||
|
"array-callback-return": "error",
|
||||||
|
"block-scoped-var": "error",
|
||||||
|
"consistent-return": "error",
|
||||||
|
"consistent-this": ["error", "that"],
|
||||||
|
"curly": ["error", "all"],
|
||||||
|
"dot-notation": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
allowKeywords: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"eqeqeq": "error",
|
||||||
|
"func-names": "error",
|
||||||
|
"guard-for-in": "error",
|
||||||
|
"max-depth": ["error", MAX_DEPTH],
|
||||||
|
"max-nested-callbacks": ["error", MAX_NESTED_CALLBACKS],
|
||||||
|
"max-params": ["error", MAX_PARAMS],
|
||||||
|
"new-cap": "error",
|
||||||
|
"no-alert": "error",
|
||||||
|
"no-array-constructor": "error",
|
||||||
|
"no-bitwise": "error",
|
||||||
|
"no-caller": "error",
|
||||||
|
"no-case-declarations": "error",
|
||||||
|
"no-class-assign": "error",
|
||||||
|
"no-cond-assign": "error",
|
||||||
|
"no-const-assign": "error",
|
||||||
|
"no-constant-condition": "error",
|
||||||
|
"no-control-regex": "error",
|
||||||
|
"no-debugger": "error",
|
||||||
|
"no-delete-var": "error",
|
||||||
|
"no-div-regex": "error",
|
||||||
|
"no-dupe-args": "error",
|
||||||
|
"no-dupe-keys": "error",
|
||||||
|
"no-duplicate-case": "error",
|
||||||
|
"no-else-return": "error",
|
||||||
|
"no-empty": "error",
|
||||||
|
"no-empty-character-class": "error",
|
||||||
|
"no-empty-function": "error",
|
||||||
|
"no-labels": "error",
|
||||||
|
"no-eq-null": "error",
|
||||||
|
"no-eval": "error",
|
||||||
|
"no-ex-assign": "error",
|
||||||
|
"no-extend-native": "error",
|
||||||
|
"no-extra-bind": "error",
|
||||||
|
"no-extra-boolean-cast": "error",
|
||||||
|
"no-extra-label": "error",
|
||||||
|
"no-fallthrough": "error",
|
||||||
|
"no-func-assign": "error",
|
||||||
|
"no-implied-eval": "error",
|
||||||
|
"no-implicit-coercion": "error",
|
||||||
|
"no-implicit-globals": "error",
|
||||||
|
"no-inner-declarations": ["error", "functions"],
|
||||||
|
"no-invalid-regexp": "error",
|
||||||
|
"no-irregular-whitespace": "error",
|
||||||
|
"no-iterator": "error",
|
||||||
|
"no-invalid-this": "error",
|
||||||
|
"no-label-var": "error",
|
||||||
|
"no-lone-blocks": "error",
|
||||||
|
"no-lonely-if": "error",
|
||||||
|
"no-loop-func": "error",
|
||||||
|
"no-magic-numbers": ["error", { ignore: [0, 1, -1] }],
|
||||||
|
"no-multi-str": "error",
|
||||||
|
"no-negated-condition": "error",
|
||||||
|
"no-nested-ternary": "error",
|
||||||
|
"no-new": "error",
|
||||||
|
"no-new-func": "error",
|
||||||
|
"no-new-wrappers": "error",
|
||||||
|
"no-obj-calls": "error",
|
||||||
|
"no-octal": "error",
|
||||||
|
"no-octal-escape": "error",
|
||||||
|
"no-param-reassign": "error",
|
||||||
|
"no-proto": "error",
|
||||||
|
"no-redeclare": "error",
|
||||||
|
"no-regex-spaces": "error",
|
||||||
|
"no-restricted-syntax": ["error", "WithStatement"],
|
||||||
|
"no-script-url": "error",
|
||||||
|
"no-self-assign": "error",
|
||||||
|
"no-self-compare": "error",
|
||||||
|
"no-sequences": "error",
|
||||||
|
"no-shadow": "error",
|
||||||
|
"no-shadow-restricted-names": "error",
|
||||||
|
"no-sparse-arrays": "error",
|
||||||
|
"no-this-before-super": "error",
|
||||||
|
"no-throw-literal": "error",
|
||||||
|
"no-trailing-spaces": "error",
|
||||||
|
"no-undef": "error",
|
||||||
|
"no-undef-init": "error",
|
||||||
|
"no-unexpected-multiline": "error",
|
||||||
|
"no-useless-constructor": "error",
|
||||||
|
"no-unmodified-loop-condition": "error",
|
||||||
|
"no-unneeded-ternary": "error",
|
||||||
|
"no-unreachable": "error",
|
||||||
|
"no-unused-expressions": "error",
|
||||||
|
"no-unused-labels": "error",
|
||||||
|
"no-use-before-define": "error",
|
||||||
|
"no-useless-call": "error",
|
||||||
|
"no-dupe-class-members": "error",
|
||||||
|
"no-var": "error",
|
||||||
|
"no-void": "error",
|
||||||
|
"no-with": "error",
|
||||||
|
"prefer-arrow-callback": "error",
|
||||||
|
"prefer-const": "error",
|
||||||
|
"prefer-rest-params": "error",
|
||||||
|
"prefer-spread": "error",
|
||||||
|
"prefer-template": "error",
|
||||||
|
"radix": "error",
|
||||||
|
"require-yield": "error",
|
||||||
|
"strict": ["error", "global"],
|
||||||
|
"use-isnan": "error",
|
||||||
|
"valid-typeof": "error",
|
||||||
|
"vars-on-top": "error",
|
||||||
|
"yoda": ["error", "never"],
|
||||||
|
|
||||||
|
"no-unused-vars": "off",
|
||||||
|
"no-console": ["error", { allow: ["debug", "warn", "error"] }],
|
||||||
|
"@typescript-eslint/ban-ts-comment": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
argsIgnorePattern: "^_",
|
||||||
|
varsIgnorePattern: "^_",
|
||||||
|
caughtErrorsIgnorePattern: "^_",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default [
|
||||||
|
// You would not believe how much this change has frustrated users: ["if an ignores key is used
|
||||||
|
// without any other keys in the configuration object, then the patterns act as global
|
||||||
|
// ignores"](https://eslint.org/docs/latest/use/configure/ignore)
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
"dist/",
|
||||||
|
".wireit/",
|
||||||
|
// don't ever lint node_modules
|
||||||
|
"node_modules/",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
eslint.configs.recommended,
|
||||||
|
wcconf.configs["flat/recommended"],
|
||||||
|
litconf.configs["flat/recommended"],
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
parser: tsparser,
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 12,
|
||||||
|
sourceType: "module",
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
files: ["src/**"],
|
||||||
|
rules,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
parser: tsparser,
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 12,
|
||||||
|
sourceType: "module",
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
...globals.nodeBuiltin,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
files: ["scripts/*.mjs", "*.ts", "*.mjs"],
|
||||||
|
rules,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
parser: tsparser,
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 12,
|
||||||
|
sourceType: "module",
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
...globals.nodeBuiltin,
|
||||||
|
...globals.jest,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
files: ["src/**/*.test.ts"],
|
||||||
|
rules,
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,73 @@
|
||||||
|
import eslint from "@eslint/js";
|
||||||
|
import tsparser from "@typescript-eslint/parser";
|
||||||
|
import litconf from "eslint-plugin-lit";
|
||||||
|
import wcconf from "eslint-plugin-wc";
|
||||||
|
import globals from "globals";
|
||||||
|
import tseslint from "typescript-eslint";
|
||||||
|
|
||||||
|
export default [
|
||||||
|
// You would not believe how much this change has frustrated users: ["if an ignores key is used
|
||||||
|
// without any other keys in the configuration object, then the patterns act as global
|
||||||
|
// ignores"](https://eslint.org/docs/latest/use/configure/ignore)
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
"dist/",
|
||||||
|
".wireit/",
|
||||||
|
// don't ever lint node_modules
|
||||||
|
"node_modules/",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
eslint.configs.recommended,
|
||||||
|
wcconf.configs["flat/recommended"],
|
||||||
|
litconf.configs["flat/recommended"],
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
parser: tsparser,
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 12,
|
||||||
|
sourceType: "module",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
files: ["src/**"],
|
||||||
|
rules: {
|
||||||
|
"no-unused-vars": "off",
|
||||||
|
"no-console": ["error", { allow: ["debug", "warn", "error"] }],
|
||||||
|
"@typescript-eslint/ban-ts-comment": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
argsIgnorePattern: "^_",
|
||||||
|
varsIgnorePattern: "^_",
|
||||||
|
caughtErrorsIgnorePattern: "^_",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
parser: tsparser,
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 12,
|
||||||
|
sourceType: "module",
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
...globals.nodeBuiltin,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
files: ["scripts/*.mjs", "*.ts", "*.mjs"],
|
||||||
|
rules: {
|
||||||
|
"no-unused-vars": "off",
|
||||||
|
"no-console": "off",
|
||||||
|
"@typescript-eslint/ban-ts-comment": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
argsIgnorePattern: "^_",
|
||||||
|
varsIgnorePattern: "^_",
|
||||||
|
caughtErrorsIgnorePattern: "^_",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { type KnipConfig } from "knip";
|
||||||
|
|
||||||
|
const config: KnipConfig = {
|
||||||
|
"entry": [
|
||||||
|
"./src/index.ts",
|
||||||
|
],
|
||||||
|
"project": ["src/**/*.ts", "src/**/*.js", "./scripts/*.mjs"],
|
||||||
|
// "ignore": ["src/**/*.test.ts", "src/**/*.stories.ts"],
|
||||||
|
// Prevent Knip from complaining about web components, which export their classes but also
|
||||||
|
// export their registration, and we don't always use both.
|
||||||
|
"ignoreExportsUsedInFile": true,
|
||||||
|
"typescript": {
|
||||||
|
config: ["tsconfig.json"],
|
||||||
|
},
|
||||||
|
"wireit": {
|
||||||
|
config: ["package.json"],
|
||||||
|
},
|
||||||
|
"eslint": {
|
||||||
|
entry: [
|
||||||
|
"eslint.config.mjs",
|
||||||
|
"scripts/eslint.precommit.mjs",
|
||||||
|
"scripts/eslint.nightmare.mjs",
|
||||||
|
"scripts/eslint.mjs",
|
||||||
|
],
|
||||||
|
config: ["package.json"],
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { LitElement, html, css } from "lit";
|
||||||
|
import { customElement } from "lit/decorators/custom-element.js";
|
||||||
|
import "./fridge-tile.js";
|
||||||
|
|
||||||
|
@customElement("fridge-magnets")
|
||||||
|
export class FridgeMagnets extends LitElement {
|
||||||
|
static get styles() {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
#fridgemagnets {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 100fr 18ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
#fridge {
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
background: url("./dist/pingbg.png") repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footer {
|
||||||
|
background-color: #32cd32;
|
||||||
|
width: 100%;
|
||||||
|
height: 18ex;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html` <div id="fridgemagnets">
|
||||||
|
<div id="fridge">
|
||||||
|
<fridge-tile word="Magnificent"></fridge-tile>
|
||||||
|
</div>
|
||||||
|
<div id="footer">Footer</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"fridge-magnets": FridgeMagnets;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,154 @@
|
||||||
|
import { LitElement, html, css } from "lit";
|
||||||
|
import { customElement } from "lit/decorators/custom-element.js";
|
||||||
|
import { property } from "lit/decorators/property.js";
|
||||||
|
import { styleMap } from "lit/directives/style-map.js";
|
||||||
|
import { LitDragEvent, LitDraggable } from "./lit-draggable.js";
|
||||||
|
|
||||||
|
export function bound(_target: unknown, key: string, descriptor: PropertyDescriptor): PropertyDescriptor {
|
||||||
|
if (typeof descriptor?.value !== "function") {
|
||||||
|
throw new Error("Only methods can be @bound.");
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
configurable: true,
|
||||||
|
get() {
|
||||||
|
const method = descriptor.value.bind(this);
|
||||||
|
Object.defineProperty(this, key, { value: method, configurable: true, writable: true });
|
||||||
|
return method;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement("fridge-tile")
|
||||||
|
export class FridgeTile extends LitElement {
|
||||||
|
@property({ type: String })
|
||||||
|
word = "";
|
||||||
|
|
||||||
|
dragHandle: LitDraggable;
|
||||||
|
|
||||||
|
static get styles() {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([data-dragging="idle"]) {
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([data-dragging="dragging"]) {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word {
|
||||||
|
font-family: Georgia, Palatino, "Palatino Linotype", Times, "Times New Roman", serif;
|
||||||
|
box-shadow: 0 0 0.375rem 0.125rem #aaa;
|
||||||
|
text-align: center;
|
||||||
|
user-select: none;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #444;
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
padding: 0.1875rem 0.25rem 0.25rem 0.25rem;
|
||||||
|
position: relative;
|
||||||
|
background: white;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word.dragging {
|
||||||
|
font-size: 1.1875rem;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.dragHandle = new LitDraggable(this);
|
||||||
|
this.onDragEnd = this.onDragEnd.bind(this);
|
||||||
|
this.addEventListener("lit-drag-end", this.onDragEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
onDragEnd(ev: LitDragEvent) {
|
||||||
|
this.style.setProperty("top", `${+ev.offsetY}px`);
|
||||||
|
this.style.setProperty("left", `${+ev.offsetX}px`);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const styles = styleMap({ width: `${this.word.length}ch` });
|
||||||
|
return html`<div part="word" style="${styles}" class="word">${this.word}</div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// base_style:
|
||||||
|
// 'font-size': "15px"
|
||||||
|
//
|
||||||
|
// drag_style:
|
||||||
|
// 'font-size': "19px"
|
||||||
|
//
|
||||||
|
// visible: false
|
||||||
|
//
|
||||||
|
// # Initial tilt.
|
||||||
|
// rotation: (Math.random() * 30) - 15
|
||||||
|
//
|
||||||
|
// constructor: (@word, @board, @master) ->
|
||||||
|
// super()
|
||||||
|
// @el = $('<div class="word">' + @word.w + '</div>')
|
||||||
|
// @el.css @base_style
|
||||||
|
// @board.append(@el)
|
||||||
|
// @rotation = (Math.random() * 30) - 15
|
||||||
|
//
|
||||||
|
// @el.draggable
|
||||||
|
// helper: "original"
|
||||||
|
// refreshPositions: false
|
||||||
|
// revertDuration: 1
|
||||||
|
//
|
||||||
|
// start: (event) =>
|
||||||
|
// mod = (Math.random() * 16) - 8
|
||||||
|
// @rotation = if Math.abs(@rotation + mod) > 15 then @rotation - mod else @rotation + mod
|
||||||
|
// style = clone(@drag_style)
|
||||||
|
// style.rotate = @rotation
|
||||||
|
// @el.animate(style, 200, () => @new_width = @el.width())
|
||||||
|
// true
|
||||||
|
//
|
||||||
|
// stop: (event) =>
|
||||||
|
// # Drop the thing dead center, at least on the x-axis,
|
||||||
|
// # and animate its return to the new font size.
|
||||||
|
// mod = (Math.random() * 16) - 8
|
||||||
|
// @rotation = if Math.abs(@rotation + mod) > 15 then @rotation - mod else @rotation + mod
|
||||||
|
// style = clone(@base_style)
|
||||||
|
// style.rotate = @rotation
|
||||||
|
// style['left'] = parseInt(@el.position().left + (0.5 * (@new_width - @width())))
|
||||||
|
// @el.animate style, 200, 'easeOutQuad', () =>
|
||||||
|
// @reset_dims()
|
||||||
|
// explode_hearts(@board, @)
|
||||||
|
// @master.poemed(@)
|
||||||
|
// true
|
||||||
|
//
|
||||||
|
// fadeOut: -> $.Deferred((d) => @el.fadeOut('fast', (() => @unset_dims(); @visible = false; d.resolve()))).promise()
|
||||||
|
//
|
||||||
|
// # Shape for deteriming poemed collision
|
||||||
|
// fuzzyshape: -> shape @left() - WIDTH_FUZZ, @top() - HEIGHT_FUZZ, @width() + (2 * WIDTH_FUZZ), @height() + (2 * HEIGHT_FUZZ)
|
||||||
|
//
|
||||||
|
// get_new_pos: ->
|
||||||
|
// bh = => parseInt(Math.random() * (@board.height() - @height()) * 0.985)
|
||||||
|
// bw = => parseInt(Math.random() * (@board.width() - @width()) * 0.98)
|
||||||
|
// [top, left] = [bh(), bw()]
|
||||||
|
// [top, left] = [bh(), bw()] until @master.unoccupied(left, top, @width(), @height())
|
||||||
|
// [top, left]
|
||||||
|
//
|
||||||
|
// flyIn: ->
|
||||||
|
// fd = (mod) ->
|
||||||
|
// m = parseInt(40 * Math.random())
|
||||||
|
// if (Math.random() < 0.5) then mod + m else -1 * m
|
||||||
|
// @el.css
|
||||||
|
// left: fd(@board.width())
|
||||||
|
// top: fd(@board.height())
|
||||||
|
// dfd = $.Deferred()
|
||||||
|
// x = Math.random()
|
||||||
|
// [top, left] = @get_new_pos()
|
||||||
|
// @el.fadeIn().animate {top: top, left: left, rotate: @rotation}, 1500, 'easeOutQuint', () =>
|
||||||
|
// @visible = true
|
||||||
|
// dfd.resolve()
|
||||||
|
// dfd.promise()
|
||||||
|
//
|
||||||
|
//
|
|
@ -1,36 +0,0 @@
|
||||||
!!! 5
|
|
||||||
%html{:xmlns => "http://www.w3.org/1999/xhtml"}
|
|
||||||
%head
|
|
||||||
%meta{:content => "text/html; charset=utf-8", "http-equiv" => "Content-Type"}/
|
|
||||||
%meta{"http-equiv" => "Acesss-Control-Allow-Origin", :content => "*"}/
|
|
||||||
%title Fridge Magnets in HTML5
|
|
||||||
%link{:href => "style.css", :rel => "stylesheet", :type => "text/css"}/
|
|
||||||
%link{:href => "ui-lightness/jquery-ui-1.8.18.custom.css", :rel => "stylesheet", :type => "text/css"}/
|
|
||||||
%body
|
|
||||||
#board
|
|
||||||
#results
|
|
||||||
#footer
|
|
||||||
#stripe
|
|
||||||
#muteunmute(data-state='on')
|
|
||||||
%img(src="unmute.png")
|
|
||||||
%button(id="shuffler") Shuffle
|
|
||||||
|
|
||||||
%p#f1
|
|
||||||
HTML5 implementation by <a href="http://elfsternberg.com">Elf M. Sternberg</a>. You can see all our poems
|
|
||||||
<a href="https://twitter.com/#!/html5magnets">@html5magnets</a> on Twitter.
|
|
||||||
#f2
|
|
||||||
%div
|
|
||||||
Comments and feedback to <a href="mailto:elf.sternberg@gmail.com">elf.sternberg@gmail.com</a> | inspired by <a href="http://twittermagnets.com/">twittermagents.com</a> and an allergic reaction to all things flash.</p>
|
|
||||||
%p The music is <em><span xmlns:dc="http://purl.org/dc/elements/1.1/" href="http://purl.org/dc/dcmitype/Sound" property="dc:title" rel="dc:type">Ethereal Space</span></em> by <a xmlns:cc="http://creativecommons.org/ns#" href="http://ccmixter.org/files/snowflake/33318" property="cc:attributionName" rel="cc:attributionURL">snowflake</a> and is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by/3.0/">Creative Commons Attribution (3.0)</a> license.
|
|
||||||
%div(style="clear:both")/
|
|
||||||
|
|
||||||
#message(style="display: none")
|
|
||||||
%p
|
|
||||||
|
|
||||||
%script{:src => "js/jquery-1.7.1.min.js", :type => "text/javascript"}
|
|
||||||
%script{:src => "js/jquery-ui-1.8.18.custom.min.js", :type => "text/javascript"}
|
|
||||||
%script{:src => "js/jquery-css-transform.js", :type => "text/javascript"}
|
|
||||||
%script{:src => "js/jquery-animate-css-rotate-scale.js", :type => "text/javascript"}
|
|
||||||
%script{:src => "js/buzz.js", :type => "text/javascript"}
|
|
||||||
%script{:src => "js/sat.js", :type => "text/javascript"}
|
|
||||||
%script{:src => "js/magnets.js", :type => "text/javascript"}
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
import "./fridge-magnets.js";
|
||||||
|
import "./fridge-tile.js";
|
|
@ -0,0 +1,150 @@
|
||||||
|
import { ReactiveControllerHost, ReactiveController } from "lit";
|
||||||
|
|
||||||
|
export type DragBoundaries = {
|
||||||
|
top: number;
|
||||||
|
left: number;
|
||||||
|
bottom: number;
|
||||||
|
right: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DragOptions = {
|
||||||
|
preventSelect: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DragAxes = "x" | "y" | "both" | "none";
|
||||||
|
|
||||||
|
export type DragBounds = HTMLElement | Partial<DragBoundaries> | "parent" | "body" | (string & Record<never, never>);
|
||||||
|
|
||||||
|
export interface LitDragEvent extends Event {
|
||||||
|
offsetX: number;
|
||||||
|
offsetY: number;
|
||||||
|
node: HTMLElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeLitDragEvent(name: string): LitDragEvent {
|
||||||
|
class _LitDragEvent extends Event implements LitDragEvent {
|
||||||
|
static readonly eventName = name;
|
||||||
|
offsetX: number = 0;
|
||||||
|
offsetY: number = 0;
|
||||||
|
node: HTMLElement;
|
||||||
|
// container: HTMLElement;
|
||||||
|
constructor(source: LitDraggable) {
|
||||||
|
super(_LitDragEvent.eventName, { bubbles: true, composed: true });
|
||||||
|
this.offsetX = source.translateX;
|
||||||
|
this.offsetY = source.translateY;
|
||||||
|
this.node = source.host;
|
||||||
|
// this.container = source.container;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _LitDragEvent as unknown as LitDragEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LitDragStart = makeLitDragEvent("lit-drag-start");
|
||||||
|
|
||||||
|
export const LitDragging = makeLitDragEvent("lit-dragging");
|
||||||
|
|
||||||
|
export const LitDragEnd: LitDragEvent = makeLitDragEvent("lit-drag-end");
|
||||||
|
|
||||||
|
const defaultOptions: DragOptions = {
|
||||||
|
preventSelect: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export class LitDraggable implements ReactiveController {
|
||||||
|
host: ReactiveControllerHost & HTMLElement;
|
||||||
|
|
||||||
|
options: DragOptions;
|
||||||
|
|
||||||
|
translateX = 0;
|
||||||
|
translateY = 0;
|
||||||
|
|
||||||
|
initialX = 0;
|
||||||
|
initialY = 0;
|
||||||
|
|
||||||
|
dragging = false;
|
||||||
|
|
||||||
|
currentSelect?: string;
|
||||||
|
|
||||||
|
pointers = new Set<number>();
|
||||||
|
|
||||||
|
constructor(host: ReactiveControllerHost & HTMLElement, options: DragOptions = defaultOptions) {
|
||||||
|
(this.host = host).addController(this);
|
||||||
|
this.options = options;
|
||||||
|
this.dragStart = this.dragStart.bind(this);
|
||||||
|
this.drag = this.drag.bind(this);
|
||||||
|
this.dragEnd = this.dragEnd.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Make this work with a handle, which will be a `ref` object.
|
||||||
|
hostConnected() {
|
||||||
|
console.log(this.host);
|
||||||
|
this.host.addEventListener("pointerdown", this.dragStart);
|
||||||
|
}
|
||||||
|
|
||||||
|
hostDisconnected() {
|
||||||
|
this.host.removeEventListener("pointerdown", this.dragStart);
|
||||||
|
}
|
||||||
|
|
||||||
|
dragStart(ev: PointerEvent) {
|
||||||
|
if (ev.button === 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("pointerup", this.dragEnd);
|
||||||
|
document.addEventListener("pointermove", this.drag);
|
||||||
|
|
||||||
|
const event_target = ev.composedPath()[0] as HTMLElement;
|
||||||
|
if (!(event_target === this.host || this.host.shadowRoot?.contains(event_target))) {
|
||||||
|
console.log("HUH?");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pointers.add(ev.pointerId);
|
||||||
|
this.dragging = true;
|
||||||
|
|
||||||
|
if (this.options.preventSelect) {
|
||||||
|
this.currentSelect = document.body.style.userSelect;
|
||||||
|
document.body.style.userSelect = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
this.initialX = ev.clientX;
|
||||||
|
this.initialY = ev.clientY;
|
||||||
|
this.host.dataset.litDrag = "true";
|
||||||
|
this.host.dispatchEvent(new LitDragStart(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
drag(ev: PointerEvent) {
|
||||||
|
if (!this.dragging || !this.pointers.has(ev.pointerId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.host.dataset.litDragging = "true";
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
this.translateX = ev.clientX - this.initialX;
|
||||||
|
this.translateY = ev.clientY - this.initialY;
|
||||||
|
this.host.dispatchEvent(new LitDragging(this));
|
||||||
|
this.host.style.setProperty("transform", `translate3d(${+this.translateX}px, ${+this.translateY}px, 0)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
dragEnd(ev: PointerEvent) {
|
||||||
|
document.removeEventListener("pointerup", this.dragEnd);
|
||||||
|
document.removeEventListener("pointermove", this.drag);
|
||||||
|
|
||||||
|
if (!this.dragging || !this.pointers.has(ev.pointerId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.pointers.delete(ev.pointerId);
|
||||||
|
this.dragging = false;
|
||||||
|
|
||||||
|
delete this.host.dataset.litDrag;
|
||||||
|
delete this.host.dataset.litDragging;
|
||||||
|
if (this.currentSelect) {
|
||||||
|
document.body.style.userSelect = this.currentSelect;
|
||||||
|
this.currentSelect = undefined;
|
||||||
|
}
|
||||||
|
this.initialX = this.translateX;
|
||||||
|
this.initialY = this.translateY;
|
||||||
|
this.host.dispatchEvent(new LitDragEnd(this));
|
||||||
|
this.host.style.removeProperty("transform");
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,530 +0,0 @@
|
||||||
SUFFIX = 1
|
|
||||||
PREFIX = 2
|
|
||||||
|
|
||||||
|
|
||||||
# Average number of words visible on any given iteration.
|
|
||||||
AVG_VISIBLE = 60
|
|
||||||
|
|
||||||
clone = (obj) ->
|
|
||||||
return obj if not obj? or typeof obj isnt 'object'
|
|
||||||
newInstance = new obj.constructor()
|
|
||||||
for key of obj
|
|
||||||
newInstance[key] = clone obj[key]
|
|
||||||
newInstance
|
|
||||||
|
|
||||||
|
|
||||||
HEIGHT_FUZZ = 8
|
|
||||||
WIDTH_FUZZ = 6
|
|
||||||
|
|
||||||
# A dimensioned object is one that appears on the board: it has an X
|
|
||||||
# and Y coordinate, a width and a height. From this, we can create a
|
|
||||||
# bounding box using the "shape" function. Dimensioned objects can be
|
|
||||||
# compared to other dimensioned objects to assert whether or not
|
|
||||||
# they're in collision. Some objects have bounding boxes that pull in
|
|
||||||
# or push out the borders abstractly, in order to provide for "fuzzy"
|
|
||||||
# collisions that correspond to drop shadows or similar visual effects.
|
|
||||||
|
|
||||||
shape = (x, y, w, h) -> [{x: x, y: y}, {x: x + w, y: y}, {x: x + w, y: y + h}, {x: x, y: y + h}]
|
|
||||||
|
|
||||||
class Dimensioned
|
|
||||||
_width: null
|
|
||||||
_height: null
|
|
||||||
_left_p: null
|
|
||||||
_top_p: null
|
|
||||||
_left: null
|
|
||||||
_top: null
|
|
||||||
_pos: null
|
|
||||||
|
|
||||||
constructor: (@el) ->
|
|
||||||
|
|
||||||
unset_dims: ->
|
|
||||||
@_left = @_top = @_width = @_height = @_pos = null
|
|
||||||
|
|
||||||
reset_dims: ->
|
|
||||||
@unset_dims()
|
|
||||||
[@left(), @top(), @width(), @height()]
|
|
||||||
|
|
||||||
positioned: -> return @_width? and @height?
|
|
||||||
|
|
||||||
visibleReposition: ->
|
|
||||||
@reposition()
|
|
||||||
@el.css {top: @top(), left: @left()}
|
|
||||||
@
|
|
||||||
|
|
||||||
reposition: ->
|
|
||||||
parent = @el.offsetParent()
|
|
||||||
[@_top, @_left] = [parseInt(@_top_p * parent.height()), parseInt(@_left_p * parent.width())]
|
|
||||||
@_pos = {left: @_left, top: @_top}
|
|
||||||
@
|
|
||||||
|
|
||||||
width: -> @_width = if @_width? then @_width else @el.outerWidth()
|
|
||||||
height: -> @_height = if @_height? then @_height else @el.outerHeight()
|
|
||||||
pos: -> @_pos = if @_pos? then @_pos else @el.position()
|
|
||||||
|
|
||||||
left: -> @_left = if @_left?
|
|
||||||
@_left
|
|
||||||
else
|
|
||||||
@_left = @pos().left
|
|
||||||
@_left_p = @_left / @el.offsetParent().width()
|
|
||||||
@_left
|
|
||||||
|
|
||||||
top: -> @_top = if @_top?
|
|
||||||
@_top
|
|
||||||
else
|
|
||||||
@_top = @pos().top
|
|
||||||
@_top_p = @_top / @el.offsetParent().height()
|
|
||||||
@_top
|
|
||||||
|
|
||||||
dims: -> [@width(), @height()]
|
|
||||||
shape: ->
|
|
||||||
shape @left(), @top(), @width(), @height()
|
|
||||||
|
|
||||||
|
|
||||||
# I can't decide if this is the right way to go, with a two-pass "set
|
|
||||||
# it all up, then make it all blow up," but it works quite well, all
|
|
||||||
# things considered. And after much consideration (like, one minute
|
|
||||||
# of realizing I never, ever used the features) it became obvious I
|
|
||||||
# didn't need Dimensioned.
|
|
||||||
|
|
||||||
class Heart
|
|
||||||
|
|
||||||
constructor: (@parent, @top, @left, symbol) ->
|
|
||||||
dv = '<div class="heart" style="display:none;top:' + parseInt(@top) + 'px;left:' + \
|
|
||||||
parseInt(@left) + 'px' + '">' + symbol + '</div>'
|
|
||||||
@el = $(dv)
|
|
||||||
@el.css {'font-size': 'larger'} if Math.random() > 0.6
|
|
||||||
@rot_dist = parseInt(90 * Math.random()) * (if Math.random() < 0.5 then 1 else -1)
|
|
||||||
[@dir, @dst, @dur] = [Math.random() * 2 * Math.PI, Math.random() * 110, Math.random() * 1200 + 700]
|
|
||||||
$(@parent).append(@el)
|
|
||||||
|
|
||||||
explode: ->
|
|
||||||
el = $(@el)
|
|
||||||
el.show().animate({opacity: 0.0, top: parseInt(@top + (Math.sin(@dir) * @dst)), left: parseInt(@left + (Math.cos(@dir) * @dst)), rotate: @rot_dist}, @dur, "easeOutCubic", (() -> el.remove()))
|
|
||||||
|
|
||||||
explode_hearts = (@board, @el) ->
|
|
||||||
randomsymbol = -> ['♪','★','✶'][parseInt(Math.random() * 3)]
|
|
||||||
symbol = if Math.random() < 0.3 then randomsymbol() else '♥'
|
|
||||||
parent = @board.el
|
|
||||||
[top, left, height, width] = [@el.top(), @el.left(), @el.height(), @el.width()]
|
|
||||||
hearts = for i in [0..(22 + (6 - Math.floor(Math.random() * 12)))]
|
|
||||||
new Heart(parent, top + (0.5 * height), left + (0.5 * width), symbol)
|
|
||||||
(h.explode() for h in hearts)
|
|
||||||
|
|
||||||
|
|
||||||
# The board is the principle object on which all other objects are
|
|
||||||
# dependent. I decided to make it a 'Dimensioned' because I'm going
|
|
||||||
# to be constantly querying its height and width.
|
|
||||||
|
|
||||||
class Board extends Dimensioned
|
|
||||||
|
|
||||||
append: (ob) -> @el.append(ob)
|
|
||||||
|
|
||||||
css: (width, height) ->
|
|
||||||
@el.css
|
|
||||||
width: width
|
|
||||||
height: height
|
|
||||||
@reset_dims()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Footer extends Dimensioned
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# A Tile is a word tile. It has a single word.
|
|
||||||
|
|
||||||
class Tile extends Dimensioned
|
|
||||||
|
|
||||||
base_style:
|
|
||||||
'font-size': "15px"
|
|
||||||
|
|
||||||
drag_style:
|
|
||||||
'font-size': "19px"
|
|
||||||
|
|
||||||
visible: false
|
|
||||||
|
|
||||||
# Initial tilt.
|
|
||||||
rotation: (Math.random() * 30) - 15
|
|
||||||
|
|
||||||
constructor: (@word, @board, @master) ->
|
|
||||||
@el = $('<div class="word">' + @word.w + '</div>')
|
|
||||||
@el.css @base_style
|
|
||||||
@board.append(@el)
|
|
||||||
@rotation = (Math.random() * 30) - 15
|
|
||||||
|
|
||||||
@el.draggable
|
|
||||||
helper: "original"
|
|
||||||
refreshPositions: false
|
|
||||||
revertDuration: 1
|
|
||||||
|
|
||||||
start: (event) =>
|
|
||||||
mod = (Math.random() * 16) - 8
|
|
||||||
@rotation = if Math.abs(@rotation + mod) > 15 then @rotation - mod else @rotation + mod
|
|
||||||
style = clone(@drag_style)
|
|
||||||
style.rotate = @rotation
|
|
||||||
@el.animate(style, 200, () => @new_width = @el.width())
|
|
||||||
true
|
|
||||||
|
|
||||||
stop: (event) =>
|
|
||||||
# Drop the thing dead center, at least on the x-axis,
|
|
||||||
# and animate its return to the new font size.
|
|
||||||
mod = (Math.random() * 16) - 8
|
|
||||||
@rotation = if Math.abs(@rotation + mod) > 15 then @rotation - mod else @rotation + mod
|
|
||||||
style = clone(@base_style)
|
|
||||||
style.rotate = @rotation
|
|
||||||
style['left'] = parseInt(@el.position().left + (0.5 * (@new_width - @width())))
|
|
||||||
@el.animate style, 200, 'easeOutQuad', () =>
|
|
||||||
@reset_dims()
|
|
||||||
explode_hearts(@board, @)
|
|
||||||
@master.poemed(@)
|
|
||||||
true
|
|
||||||
|
|
||||||
fadeOut: -> $.Deferred((d) => @el.fadeOut('fast', (() => @unset_dims(); @visible = false; d.resolve()))).promise()
|
|
||||||
|
|
||||||
# Shape for deteriming poemed collision
|
|
||||||
fuzzyshape: -> shape @left() - WIDTH_FUZZ, @top() - HEIGHT_FUZZ, @width() + (2 * WIDTH_FUZZ), @height() + (2 * HEIGHT_FUZZ)
|
|
||||||
|
|
||||||
get_new_pos: ->
|
|
||||||
bh = => parseInt(Math.random() * (@board.height() - @height()) * 0.985)
|
|
||||||
bw = => parseInt(Math.random() * (@board.width() - @width()) * 0.98)
|
|
||||||
[top, left] = [bh(), bw()]
|
|
||||||
[top, left] = [bh(), bw()] until @master.unoccupied(left, top, @width(), @height())
|
|
||||||
[top, left]
|
|
||||||
|
|
||||||
flyIn: ->
|
|
||||||
fd = (mod) ->
|
|
||||||
m = parseInt(40 * Math.random())
|
|
||||||
if (Math.random() < 0.5) then mod + m else -1 * m
|
|
||||||
@el.css
|
|
||||||
left: fd(@board.width())
|
|
||||||
top: fd(@board.height())
|
|
||||||
dfd = $.Deferred()
|
|
||||||
x = Math.random()
|
|
||||||
[top, left] = @get_new_pos()
|
|
||||||
@el.fadeIn().animate {top: top, left: left, rotate: @rotation}, 1500, 'easeOutQuint', () =>
|
|
||||||
@visible = true
|
|
||||||
dfd.resolve()
|
|
||||||
dfd.promise()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PoemDisplay extends Dimensioned
|
|
||||||
el: $('#results')
|
|
||||||
_max_box: null
|
|
||||||
dialog: $('#message')
|
|
||||||
dtimer: null
|
|
||||||
|
|
||||||
constructor: (@board) ->
|
|
||||||
@el.css({top: @board.height()})
|
|
||||||
|
|
||||||
sentSuccess: (data, textStatus) =>
|
|
||||||
$('p', @dialog).html "Your poem has been immortalized! It can be seen on Twitter at <a href='https://twitter.com/#!/html5magnets'>@html5magnets</a>."
|
|
||||||
if data.error
|
|
||||||
$('p', @dialog).html data.message
|
|
||||||
|
|
||||||
@dialog.dialog("open")
|
|
||||||
if dtimer != null
|
|
||||||
clearTimeout(dtimer)
|
|
||||||
dtimer = null
|
|
||||||
dtimer = setTimeout (() => @dialog.dialog("close")), 7500
|
|
||||||
|
|
||||||
sentError: (query, textStatus) =>
|
|
||||||
console.log(query, textStatus)
|
|
||||||
|
|
||||||
sendToServer: (haiku) =>
|
|
||||||
$.ajax 'http://html5magnets.elfsternberg.com/poems/',
|
|
||||||
type: "POST"
|
|
||||||
data: {"message": haiku}
|
|
||||||
dataType: 'json'
|
|
||||||
success: @sentSuccess
|
|
||||||
error: @sentError
|
|
||||||
|
|
||||||
update: (lines) ->
|
|
||||||
lines = (l for l in lines when l.length > 0)
|
|
||||||
if lines.length == 0
|
|
||||||
@el.fadeOut()
|
|
||||||
return
|
|
||||||
|
|
||||||
@el.html('')
|
|
||||||
@el.show()
|
|
||||||
res = for words in lines
|
|
||||||
line = words[0].w
|
|
||||||
for word in words[1...words.length]
|
|
||||||
line += if word.s == 1 then word.w else ' ' + word.w
|
|
||||||
@el.append($('<p>' + line + '</p>'))
|
|
||||||
|
|
||||||
sentence = for words in lines
|
|
||||||
line = words[0].w
|
|
||||||
for word in words[1...words.length]
|
|
||||||
line += if word.s == 1 then word.w else ' ' + word.w
|
|
||||||
line
|
|
||||||
|
|
||||||
haiku_add = 0
|
|
||||||
if sentence.length > 1
|
|
||||||
haiku = sentence.join(" / ")
|
|
||||||
if haiku.length < 140
|
|
||||||
haiku_add = 38
|
|
||||||
@el.append('<div id="tweetthis"><img src="tweetthis.png"></div>')
|
|
||||||
$('#tweetthis').click(() => @sendToServer(lines))
|
|
||||||
|
|
||||||
if lines.length != @lastlines
|
|
||||||
lh = $('p', @el).height()
|
|
||||||
setTimeout((() => @el.animate {top: @board.height() - ((lh * (lines.length + 1.7)) + haiku_add)}), 1)
|
|
||||||
@
|
|
||||||
|
|
||||||
max_box: =>
|
|
||||||
return shape(@board.height() - (16 * 6.7), 0, 480, (16 * 6.7))
|
|
||||||
|
|
||||||
|
|
||||||
# A poem is three or more *moved* words in fuzzy collision.
|
|
||||||
|
|
||||||
class Poem
|
|
||||||
words: []
|
|
||||||
|
|
||||||
constructor: (@master) ->
|
|
||||||
@poembox = new PoemDisplay(@master.board)
|
|
||||||
|
|
||||||
real_poem: (poem = null) ->
|
|
||||||
poem = @words if not poem?
|
|
||||||
if poem.length > 1 then poem else []
|
|
||||||
|
|
||||||
has: (word) ->
|
|
||||||
return (w for w in @words when w == word).length > 0
|
|
||||||
|
|
||||||
find_bbox: (words = null, sp = 0) ->
|
|
||||||
words = @words if not words
|
|
||||||
return null if words.length < 2
|
|
||||||
[ul, ur, lr, ll] = words[0].shape()
|
|
||||||
[mx, my, nx, ny] = [ul.x, ul.y, lr.x, lr.y]
|
|
||||||
for i in [1...words.length]
|
|
||||||
[ul1, ur1, lr1, ll1] = words[i].shape()
|
|
||||||
mx = ul1.x if ul1.x < mx
|
|
||||||
my = ul1.y if ul1.y < my
|
|
||||||
nx = lr1.x if lr1.x > nx
|
|
||||||
ny = lr1.y if lr1.y > ny
|
|
||||||
return [{x: mx - sp, y: my - sp}, {x: nx + sp, y: my - sp}, {x: nx + sp, y: ny + sp}, {x: nx + sp, y: my - sp}]
|
|
||||||
|
|
||||||
check_dismissal: (word) ->
|
|
||||||
# If the word is colliding with another word in the poem, it
|
|
||||||
# is not being dismissed.
|
|
||||||
fuzzyshape = word.fuzzyshape()
|
|
||||||
for w in @words
|
|
||||||
if w != word and colliding(fuzzyshape, w.fuzzyshape())
|
|
||||||
@inorder()
|
|
||||||
return @words
|
|
||||||
|
|
||||||
# Remove word from @words
|
|
||||||
@words = @real_poem(w for w in @words when w != word)
|
|
||||||
return @words if @words.length < 2
|
|
||||||
|
|
||||||
# Reconstitute poem from what remains
|
|
||||||
find_split_poem = (poem) =>
|
|
||||||
# Why 2? Because a poem of length 1 is just a word!
|
|
||||||
throw "Don't run on an empty poem!" if poem.length < 2
|
|
||||||
|
|
||||||
# Transfer all words in *poem2* that are in collision with
|
|
||||||
# words in poem1. If the poems don't change, return them,
|
|
||||||
# otherwise repeat the process.
|
|
||||||
|
|
||||||
edgefollow = (poem1, poem2) =>
|
|
||||||
to_xfr = (w2 for w2 in poem2 when \
|
|
||||||
((w1 for w1 in poem1 when \
|
|
||||||
colliding(w1.fuzzyshape(), w2.fuzzyshape())).length > 0))
|
|
||||||
|
|
||||||
# Words are not being shuffled around
|
|
||||||
return [poem1, poem2] if to_xfr.length == 0
|
|
||||||
|
|
||||||
# Else...
|
|
||||||
poem1 = poem1.concat(to_xfr)
|
|
||||||
poem2 = (w for w in poem2 when w not in poem1)
|
|
||||||
edgefollow(poem1, poem2)
|
|
||||||
|
|
||||||
wordlist = (i for i in poem)
|
|
||||||
first_word = wordlist.pop()
|
|
||||||
[lpoem, rpoem] = edgefollow([first_word], wordlist)
|
|
||||||
return [] if lpoem.length < 2 and lpoem.length < 2
|
|
||||||
return rpoem if lpoem.length < 2
|
|
||||||
return lpoem if rpoem.length < 2
|
|
||||||
return if Math.vector.magnitude(@find_bbox(lpoem)[0]) < Math.vector.magnitude(@find_bbox(rpoem)[0])
|
|
||||||
lpoem
|
|
||||||
else
|
|
||||||
rpoem
|
|
||||||
|
|
||||||
@words = @real_poem(find_split_poem(@words))
|
|
||||||
if @words
|
|
||||||
@inorder()
|
|
||||||
@words
|
|
||||||
|
|
||||||
# Looks at the bounding box for the current poem and adds any words
|
|
||||||
# to it that are in collision with the existing poem.
|
|
||||||
# :: [tiles] -> [tiles]
|
|
||||||
|
|
||||||
research_poem: (poem) ->
|
|
||||||
nbbox = @find_bbox(poem)
|
|
||||||
newpoem = (i for i in poem)
|
|
||||||
potentials = (w for w in @master.visible() when \
|
|
||||||
(w not in newpoem) and colliding(w.fuzzyshape(), nbbox))
|
|
||||||
|
|
||||||
# [word, poem] -> boolean
|
|
||||||
collides_with_existing_poem = (nw1, poem1) ->
|
|
||||||
fzs1 = nw1.fuzzyshape()
|
|
||||||
acw1 = nw1.word
|
|
||||||
((nw2 for nw2 in poem1 when \
|
|
||||||
acw1 != nw2.word and \
|
|
||||||
colliding(nw2.fuzzyshape(), fzs1)).length > 0)
|
|
||||||
|
|
||||||
addenda = (nw for nw in potentials when collides_with_existing_poem(nw, newpoem))
|
|
||||||
if addenda.length == 0 then newpoem else @research_poem(newpoem.concat(addenda))
|
|
||||||
|
|
||||||
|
|
||||||
# Looks to see if the word has come into collision with another
|
|
||||||
# word, creating a new poem.
|
|
||||||
# :: tile -> [tiles]
|
|
||||||
|
|
||||||
maybe_new_poem: (word) ->
|
|
||||||
throw "Do not call maybe_new_poem on a working poem." if @words.length > 0
|
|
||||||
|
|
||||||
fuzzyshape = word.fuzzyshape()
|
|
||||||
@words = @real_poem((w for w in @master.visible() when \
|
|
||||||
colliding(w.fuzzyshape(), fuzzyshape)))
|
|
||||||
|
|
||||||
if @words.length
|
|
||||||
@words = @research_poem(@words)
|
|
||||||
|
|
||||||
@inorder()
|
|
||||||
@words
|
|
||||||
|
|
||||||
|
|
||||||
check_for_addition: (word) ->
|
|
||||||
# See if this word collides with any of the words in our poem:
|
|
||||||
fuzzyshape = word.fuzzyshape()
|
|
||||||
for w in @words
|
|
||||||
if colliding(fuzzyshape, w.fuzzyshape()) and w != word
|
|
||||||
@words.push(word)
|
|
||||||
# One collision is all it takes.
|
|
||||||
break
|
|
||||||
|
|
||||||
@words = @research_poem(@words)
|
|
||||||
@inorder()
|
|
||||||
@words
|
|
||||||
|
|
||||||
|
|
||||||
check: (word) ->
|
|
||||||
return @words = @maybe_new_poem(word) if @words.length == 0
|
|
||||||
|
|
||||||
if @has(word)
|
|
||||||
@words = @check_dismissal(word)
|
|
||||||
return @words = if @words.length == 0 then @maybe_new_poem(word) else @words
|
|
||||||
|
|
||||||
# This word doesn't create a new poem, and it isn't present in
|
|
||||||
# our existing poem.
|
|
||||||
|
|
||||||
return @words = @check_for_addition(word)
|
|
||||||
|
|
||||||
|
|
||||||
inorder: ->
|
|
||||||
return @poembox.update([]) if @words.length < 2
|
|
||||||
nbbox = @find_bbox(@words)
|
|
||||||
avg_height = 0
|
|
||||||
for w in @words
|
|
||||||
avg_height = avg_height + w.height()
|
|
||||||
avg_height = parseInt(avg_height / @words.length)
|
|
||||||
ret = []
|
|
||||||
for i in (i for i in [nbbox[0].y...nbbox[2].y] by avg_height)
|
|
||||||
zbot = i + avg_height
|
|
||||||
zone_words = (w for w in @words when w.top() >= i and w.top() < zbot)
|
|
||||||
zone_words.sort (a, b) -> a.left() - b.left()
|
|
||||||
ret.push((i.word for i in zone_words))
|
|
||||||
@poembox.update(ret)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Magnets extends Dimensioned
|
|
||||||
|
|
||||||
constructor: (@wordlist) ->
|
|
||||||
@el = $(window)
|
|
||||||
@footer = new Footer($('#footer'))
|
|
||||||
@board = new Board($('#board'))
|
|
||||||
@recbox = $('#recbox')
|
|
||||||
@results = $('#results')
|
|
||||||
@words = (new Tile(word, @board, @) for word in @wordlist)
|
|
||||||
@resize()
|
|
||||||
@poem = new Poem(@)
|
|
||||||
$('#shuffler').click(@reword)
|
|
||||||
$(window).resize(@resize)
|
|
||||||
|
|
||||||
resize: =>
|
|
||||||
@unset_dims()
|
|
||||||
@board.css('100%', @height() - @footer.height())
|
|
||||||
(word.visibleReposition() for word in @words when word.visible)
|
|
||||||
@
|
|
||||||
|
|
||||||
unoccupied:(left, top, width, height) ->
|
|
||||||
reserved = []
|
|
||||||
if @poem.real_poem().length > 0
|
|
||||||
reserved.push(@poem.find_bbox(null, 10))
|
|
||||||
reserved.push(@poem.poembox.max_box())
|
|
||||||
target = shape(left, top, width, height)
|
|
||||||
for s in reserved
|
|
||||||
if colliding(target, s)
|
|
||||||
return false
|
|
||||||
true
|
|
||||||
|
|
||||||
visible: ->
|
|
||||||
(w for w in @words when w.visible)
|
|
||||||
|
|
||||||
poemed: (word) ->
|
|
||||||
@poem.check(word)
|
|
||||||
|
|
||||||
livewords: -> (w for w in @words when w.visible)
|
|
||||||
|
|
||||||
reword: =>
|
|
||||||
poemed = (w for w in @words when @poem.has(w))
|
|
||||||
flyprob = AVG_VISIBLE / (@words.length - poemed.length)
|
|
||||||
$.when.apply(null, (w.fadeOut() for w in @words when not @poem.has(w))).then () =>
|
|
||||||
$.when.apply(null, (w.flyIn() for w in @words when not @poem.has(w) and Math.random() < flyprob)).then () =>
|
|
||||||
(w.reset_dims() for w in @words when w.visible)
|
|
||||||
@
|
|
||||||
|
|
||||||
|
|
||||||
class MusicPlayer
|
|
||||||
constructor: (control, tunes) ->
|
|
||||||
@control = $(control)
|
|
||||||
@control.data('state', 'on')
|
|
||||||
@active = true
|
|
||||||
@music = new buzz.sound(tunes, {preload:true, autoload: true, loop: true})
|
|
||||||
@music.setVolume(0)
|
|
||||||
@music.bind 'canplaythrough', () =>
|
|
||||||
@music.play()
|
|
||||||
@music.fadeTo(60, 10000)
|
|
||||||
|
|
||||||
@control.click (ev) =>
|
|
||||||
@active = if @active then @fadeOut() else @fadeIn()
|
|
||||||
|
|
||||||
fadeOut: ->
|
|
||||||
@music.fadeOut(600, () => @music.pause())
|
|
||||||
$('img', @control).attr('src', 'mute.png')
|
|
||||||
false
|
|
||||||
|
|
||||||
fadeIn: ->
|
|
||||||
@music.play().fadeIn(1200)
|
|
||||||
$('img', @control).attr('src', 'unmute.png')
|
|
||||||
true
|
|
||||||
|
|
||||||
|
|
||||||
$ ->
|
|
||||||
$.ajax
|
|
||||||
url: 'js/wordlist.js'
|
|
||||||
data: {}
|
|
||||||
success: (data) -> (new Magnets(data)).resize().reword()
|
|
||||||
error: -> console.log(arguments)
|
|
||||||
dataType: 'json'
|
|
||||||
|
|
||||||
v = new MusicPlayer('#muteunmute',
|
|
||||||
['media/snowflake_-_Ethereal_Space.mp3',
|
|
||||||
'media/snowflake_-_Ethereal_Space.ogg'])
|
|
||||||
|
|
||||||
$( "#message" ).dialog
|
|
||||||
autoOpen: false
|
|
||||||
show: "fadeIn"
|
|
||||||
hide: "fadeOut"
|
|
Binary file not shown.
After Width: | Height: | Size: 352 KiB |
|
@ -1,97 +0,0 @@
|
||||||
#Copyright (c) 2012 Elf M. Sternberg
|
|
||||||
#
|
|
||||||
# Much of the code here I would never have understood if it hadn't
|
|
||||||
# been for the patient work of Caleb Helbling
|
|
||||||
# (http://www.propulsionjs.com/), as well as the Wikipedia pages for
|
|
||||||
# the Separating Axis Theorem. It took me a week to wrap my head
|
|
||||||
# around these ideas.
|
|
||||||
#
|
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
#of this software and associated documentation files (the "Software"), to deal
|
|
||||||
#in the Software without restriction, including without limitation the rights
|
|
||||||
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
#copies of the Software, and to permit persons to whom the Software is
|
|
||||||
#furnished to do so, subject to the following conditions:
|
|
||||||
#
|
|
||||||
#The above copyright notice and this permission notice shall be included in
|
|
||||||
#all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
#THE SOFTWARE.
|
|
||||||
|
|
||||||
Math.vector =
|
|
||||||
add: (v1, v2) -> {x: (v1.x + v2.x), y: (v1.y + v2.y)}
|
|
||||||
|
|
||||||
# Scale a given vector.
|
|
||||||
scalar: (v, s) -> {x: (v.x * s), y: (v.y * s)}
|
|
||||||
|
|
||||||
dot: (v1, v2) -> v1.x * v2.x + v1.y * v2.y
|
|
||||||
|
|
||||||
magnitude2: (v) ->
|
|
||||||
x = v.x
|
|
||||||
y = v.y
|
|
||||||
x * x + y * y
|
|
||||||
|
|
||||||
magnitude: (v) -> Math.sqrt(Math.vector.magnitude2(v))
|
|
||||||
|
|
||||||
normalize: (v) ->
|
|
||||||
mag = Math.vector.magnitude(v)
|
|
||||||
{x: (v.x / mag), y: (v.y / mag)}
|
|
||||||
|
|
||||||
leftNormal: (v) -> {x: -v.y, y: v.x}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.colliding = (shape1, shape2) ->
|
|
||||||
|
|
||||||
# Return the axes of a shape. In a polygon, each potential
|
|
||||||
# separating axis is the normal to each edge. For our purposes, a
|
|
||||||
# "shape" is an array of points with the structure [{x: 0, y: 0}, .. ]
|
|
||||||
# We assume that the final edge is from the last point back to the
|
|
||||||
# first.
|
|
||||||
|
|
||||||
genAxes = (shape) ->
|
|
||||||
throw "Cannot handle non-polygons" if shape.length < 3
|
|
||||||
|
|
||||||
# Calculate the normal of a single pair of points in the
|
|
||||||
# shape.
|
|
||||||
|
|
||||||
axis = (shape, pi) ->
|
|
||||||
p1 = shape[pi]
|
|
||||||
p2 = shape[if pi == (shape.length - 1) then 0 else pi + 1]
|
|
||||||
edge = {x: p1.x - p2.x, y: p1.y - p2.y}
|
|
||||||
Math.vector.normalize(Math.vector.leftNormal(edge))
|
|
||||||
|
|
||||||
(axis(shape, i) for i in [0...shape.length])
|
|
||||||
|
|
||||||
# Calculate the extremis of the shape "above" a given axis
|
|
||||||
|
|
||||||
genProjection = (shape, axis) ->
|
|
||||||
min = Math.vector.dot(axis, shape[0])
|
|
||||||
max = min
|
|
||||||
for i in [1...shape.length]
|
|
||||||
p = Math.vector.dot(axis, shape[i])
|
|
||||||
min = p if p < min
|
|
||||||
max = p if p > max
|
|
||||||
{min: min, max: max}
|
|
||||||
|
|
||||||
axes1 = genAxes(shape1)
|
|
||||||
axes2 = genAxes(shape2)
|
|
||||||
axes = axes1.concat axes2
|
|
||||||
for axis in axes
|
|
||||||
proj1 = genProjection(shape1, axis)
|
|
||||||
proj2 = genProjection(shape2, axis)
|
|
||||||
if not ( \
|
|
||||||
(proj1.min >= proj2.min and proj1.min <= proj2.max) or \
|
|
||||||
(proj1.max >= proj2.min and proj1.max <= proj2.max) or \
|
|
||||||
(proj2.min >= proj1.min and proj2.min <= proj1.max) or \
|
|
||||||
(proj2.max >= proj1.min and proj2.max <= proj1.max))
|
|
||||||
return false
|
|
||||||
return true
|
|
||||||
|
|
||||||
|
|
269
src/style.less
269
src/style.less
|
@ -1,269 +0,0 @@
|
||||||
/* -*- mode: css; -*- */
|
|
||||||
|
|
||||||
/* ___ ___ ___ ___ _
|
|
||||||
/ __/ __/ __| | _ \___ ___ ___| |_
|
|
||||||
| (__\__ \__ \ | / -_|_-</ -_) _|
|
|
||||||
\___|___/___/ |_|_\___/__/\___|\__|
|
|
||||||
|
|
||||||
html5doctor.com Reset Stylesheet
|
|
||||||
v1.5
|
|
||||||
Last Updated: 2010-08-12
|
|
||||||
Author: Richard Clark - http://richclarkdesign.com
|
|
||||||
Twitter: @rich_clark
|
|
||||||
*/
|
|
||||||
|
|
||||||
html, body, div, span, object, iframe,
|
|
||||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
|
||||||
abbr, address, cite, code,
|
|
||||||
del, dfn, em, img, ins, kbd, q, samp,
|
|
||||||
small, strong, sub, sup, var,
|
|
||||||
b, i,
|
|
||||||
dl, dt, dd, ol, ul, li,
|
|
||||||
fieldset, form, label, legend,
|
|
||||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
|
||||||
article, aside, canvas, details, figcaption, figure,
|
|
||||||
footer, header, hgroup, menu, nav, section, summary,
|
|
||||||
time, mark, audio, video {
|
|
||||||
margin:0;
|
|
||||||
padding:0;
|
|
||||||
border:0;
|
|
||||||
outline:0;
|
|
||||||
vertical-align:baseline;
|
|
||||||
background:transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
line-height:1;
|
|
||||||
}
|
|
||||||
|
|
||||||
article,aside,canvas,details,figcaption,figure,
|
|
||||||
footer,header,hgroup,menu,nav,section,summary {
|
|
||||||
display:block;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav ul {
|
|
||||||
list-style:none;
|
|
||||||
}
|
|
||||||
|
|
||||||
blockquote, q {
|
|
||||||
quotes:none;
|
|
||||||
}
|
|
||||||
|
|
||||||
blockquote:before, blockquote:after,
|
|
||||||
q:before, q:after {
|
|
||||||
content:'';
|
|
||||||
content:none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
margin:0;
|
|
||||||
padding:0;
|
|
||||||
font-size:100%;
|
|
||||||
vertical-align:baseline;
|
|
||||||
background:transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
margin:0;
|
|
||||||
padding:0;
|
|
||||||
font-size:100%;
|
|
||||||
vertical-align:baseline;
|
|
||||||
background:transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
ins {
|
|
||||||
background-color:#ff9;
|
|
||||||
color:#000;
|
|
||||||
text-decoration:none;
|
|
||||||
}
|
|
||||||
|
|
||||||
mark {
|
|
||||||
background-color:#ff9;
|
|
||||||
color:#000;
|
|
||||||
font-style:italic;
|
|
||||||
font-weight:bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
del {
|
|
||||||
text-decoration: line-through;
|
|
||||||
}
|
|
||||||
|
|
||||||
abbr[title], dfn[title] {
|
|
||||||
border-bottom:1px dotted #000;
|
|
||||||
cursor:help;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
border-collapse:collapse;
|
|
||||||
border-spacing:0;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
display:block;
|
|
||||||
height:1px;
|
|
||||||
border:0;
|
|
||||||
border-top:1px solid #cccccc;
|
|
||||||
margin:1em 0;
|
|
||||||
padding:0;
|
|
||||||
}
|
|
||||||
|
|
||||||
input, select {
|
|
||||||
vertical-align:middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.small-rounded {
|
|
||||||
-moz-border-radius-topleft: 5px;
|
|
||||||
-moz-border-radius-topright: 5px;
|
|
||||||
-moz-border-radius-bottomleft: 5px;
|
|
||||||
-moz-border-radius-bottomright: 5px;
|
|
||||||
-webkit-border-bottom-right-radius: 5px;
|
|
||||||
-webkit-border-top-left-radius: 5px;
|
|
||||||
-webkit-border-top-right-radius: 5px;
|
|
||||||
-webkit-border-bottom-left-radius: 5px;
|
|
||||||
border-bottom-right-radius: 5px;
|
|
||||||
border-top-left-radius: 5px;
|
|
||||||
border-top-right-radius: 5px;
|
|
||||||
border-bottom-left-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@limegreen: #32cd32;
|
|
||||||
|
|
||||||
#board {
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
background: url('pingbg.png') repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.word {
|
|
||||||
font-family: Georgia, Palatino,"Palatino Linotype", Times, "Times New Roman", serif;
|
|
||||||
-moz-box-shadow: 0 0 6px 2px #aaa;
|
|
||||||
-webkit-box-shadow: 0 0 6px 2px #aaa;
|
|
||||||
box-shadow: 0 0 6px 2px #aaa;
|
|
||||||
display: none;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-khtml-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
-o-user-select: none;
|
|
||||||
text-align: center;
|
|
||||||
user-select: none;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #444;
|
|
||||||
font-size: 13px;
|
|
||||||
padding: 3px 4px 4px 4px;
|
|
||||||
position: absolute;
|
|
||||||
background: white;
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#footer {
|
|
||||||
height: 18ex;
|
|
||||||
font-family: "Trebuchet MS", "Lucida Sans Unicode", "Lucida Grande", "Lucida Sans", Arial, sans-serif;
|
|
||||||
font-size: 12px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#stripe {
|
|
||||||
width: 100%;
|
|
||||||
margin: 0.4% 0 0.4% 0;
|
|
||||||
background-color: @limegreen;
|
|
||||||
height: 10ex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.heart {
|
|
||||||
position: absolute;
|
|
||||||
color: deeppink;
|
|
||||||
font-size: 22px;
|
|
||||||
font-weight: bold;
|
|
||||||
z-index: 30;
|
|
||||||
}
|
|
||||||
|
|
||||||
#f1 { width: 46%; float: left; padding-left: 1%;}
|
|
||||||
#f2 { width: 46%; float: right; text-align: right; padding-right: 1%;}
|
|
||||||
|
|
||||||
.recline {
|
|
||||||
position: relative;
|
|
||||||
height: 40px;
|
|
||||||
width: 100%;
|
|
||||||
clear: right;
|
|
||||||
background: url(writerect_bg.png) top left repeat-x;
|
|
||||||
}
|
|
||||||
|
|
||||||
#results {
|
|
||||||
-moz-border-radius-topright: 5px;
|
|
||||||
-webkit-border-top-right-radius: 5px;
|
|
||||||
border-top-right-radius: 5px;
|
|
||||||
border-top: 1px solid #888;
|
|
||||||
border-right: 1px solid #888;
|
|
||||||
border-bottom: 1px solid #888;
|
|
||||||
position: absolute;
|
|
||||||
color: #666;
|
|
||||||
left: 0px;
|
|
||||||
bottom: 5px;
|
|
||||||
padding: 1em;
|
|
||||||
background: url(alphamod.png) repeat;
|
|
||||||
display: none;
|
|
||||||
|
|
||||||
p {
|
|
||||||
white-space: nowrap;
|
|
||||||
line-height: 1.1;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#shuffler {
|
|
||||||
font-family: Georgia, Palatino,"Palatino Linotype", Times, "Times New Roman", serif;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: white;
|
|
||||||
position: relative;
|
|
||||||
background-color: @limegreen;
|
|
||||||
margin-top: 10px;
|
|
||||||
float: right;
|
|
||||||
cursor: pointer;
|
|
||||||
margin-right: 32px;
|
|
||||||
width: 5em;
|
|
||||||
vertical-align: middle;
|
|
||||||
border-top: 1px solid white;
|
|
||||||
border-left: 1px solid white;
|
|
||||||
border-bottom: 1px solid black;
|
|
||||||
border-right: 1px solid black;
|
|
||||||
box-shadow: 1px 3px 6px rgba(0, 0, 0, 0.80);
|
|
||||||
-moz-box-shadow: 1px 3px 6px rgba(0, 0, 0, 0.80);
|
|
||||||
-webkit-box-shadow: 1px 3px 6px rgba(0, 0, 0, 0.80);
|
|
||||||
}
|
|
||||||
|
|
||||||
#shuffler:hover {
|
|
||||||
background-color: darken(@limegreen, 10%);
|
|
||||||
}
|
|
||||||
|
|
||||||
#shuffler:active {
|
|
||||||
background-color: darken(@limegreen, 10%);
|
|
||||||
border-top: 1px solid black;
|
|
||||||
border-left: 1px solid black;
|
|
||||||
border-bottom: 1px solid white;
|
|
||||||
border-right: 1px solid white;
|
|
||||||
}
|
|
||||||
|
|
||||||
#muteunmute {
|
|
||||||
cursor: pointer;
|
|
||||||
float: right;
|
|
||||||
padding-right: 128px;
|
|
||||||
width: 42px;
|
|
||||||
height: 42px;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 42px;
|
|
||||||
height: 42px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#tweetthis {
|
|
||||||
padding-top: 6px;
|
|
||||||
}
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
html, body {
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#fridgemagnets {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
#fridge {
|
||||||
|
overflow: hidden;
|
||||||
|
width: 100vw;
|
||||||
|
background: url('pingbg.png') repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footer {
|
||||||
|
background-color: #32cd32;
|
||||||
|
width: 100vw;
|
||||||
|
height: 18ex;
|
||||||
|
}
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
testCase = require('nodeunit').testCase
|
|
||||||
require('./sat.coffee')
|
|
||||||
|
|
||||||
module.exports = testCase
|
|
||||||
"TestAddition": (test) ->
|
|
||||||
m = Math.vector.add {x: 1, y: 1}, {x: -1, y: -1}
|
|
||||||
test.ok(m.x == 0 and m.y == 0)
|
|
||||||
m = Math.vector.add {x: 1, y: 1}, {x: 1, y: 1}
|
|
||||||
test.ok(m.x == 2 and m.y == 2)
|
|
||||||
test.done()
|
|
||||||
|
|
||||||
"TestScalar": (test) ->
|
|
||||||
m = Math.vector.scalar({x: 2, y: 2}, 2)
|
|
||||||
test.ok(m.x == 4 and m.y == 4)
|
|
||||||
test.done()
|
|
||||||
|
|
||||||
"TestMag2": (test) ->
|
|
||||||
m = Math.vector.magnitude2({x: 2, y: 2})
|
|
||||||
test.ok(m == 8)
|
|
||||||
test.done()
|
|
||||||
|
|
||||||
"TestMag": (test) ->
|
|
||||||
m = Math.vector.magnitude({x: 2, y: 2})
|
|
||||||
test.ok(m == Math.sqrt(8))
|
|
||||||
test.done()
|
|
||||||
|
|
||||||
"TestNormalize": (test) ->
|
|
||||||
m = Math.vector.normalize({x: 5, y: 0})
|
|
||||||
test.ok(m.x == 1 and m.y == 0)
|
|
||||||
m = Math.vector.normalize({x: 0, y: 5})
|
|
||||||
test.ok(m.x == 0 and m.y == 1)
|
|
||||||
m = Math.vector.normalize({x: 4, y: 3})
|
|
||||||
test.ok((m.x * m.x + m.y * m.y) == 1)
|
|
||||||
test.done()
|
|
|
@ -1,210 +0,0 @@
|
||||||
[{"w": "a", "s": 0},
|
|
||||||
{"w": "a", "s": 0},
|
|
||||||
{"w": "about", "s": 0},
|
|
||||||
{"w": "above", "s": 0},
|
|
||||||
{"w": "after", "s": 0},
|
|
||||||
{"w": "all", "s": 0},
|
|
||||||
{"w": "almost", "s": 0},
|
|
||||||
{"w": "always", "s": 0},
|
|
||||||
{"w": "am", "s": 0},
|
|
||||||
{"w": "an", "s": 0},
|
|
||||||
{"w": "an", "s": 0},
|
|
||||||
{"w": "and", "s": 0},
|
|
||||||
{"w": "and", "s": 0},
|
|
||||||
{"w": "animal", "s": 0},
|
|
||||||
{"w": "apple", "s": 0},
|
|
||||||
{"w": "are", "s": 0},
|
|
||||||
{"w": "as", "s": 0},
|
|
||||||
{"w": "as", "s": 0},
|
|
||||||
{"w": "ask", "s": 0},
|
|
||||||
{"w": "at", "s": 0},
|
|
||||||
{"w": "bad", "s": 0},
|
|
||||||
{"w": "be", "s": 0},
|
|
||||||
{"w": "beauty", "s": 0},
|
|
||||||
{"w": "believe", "s": 0},
|
|
||||||
{"w": "beneath", "s": 0},
|
|
||||||
{"w": "between", "s": 0},
|
|
||||||
{"w": "bird", "s": 0},
|
|
||||||
{"w": "birthday", "s": 0},
|
|
||||||
{"w": "blend", "s": 0},
|
|
||||||
{"w": "blue", "s": 0},
|
|
||||||
{"w": "bring", "s": 0},
|
|
||||||
{"w": "but", "s": 0},
|
|
||||||
{"w": "but", "s": 0},
|
|
||||||
{"w": "butterfly", "s": 0},
|
|
||||||
{"w": "by", "s": 0},
|
|
||||||
{"w": "calendar", "s": 0},
|
|
||||||
{"w": "can", "s": 0},
|
|
||||||
{"w": "celebrate", "s": 0},
|
|
||||||
{"w": "change", "s": 0},
|
|
||||||
{"w": "cloud", "s": 0},
|
|
||||||
{"w": "cold", "s": 0},
|
|
||||||
{"w": "come", "s": 0},
|
|
||||||
{"w": "comfort", "s": 0},
|
|
||||||
{"w": "could", "s": 0},
|
|
||||||
{"w": "d", "s": 1},
|
|
||||||
{"w": "dark", "s": 0},
|
|
||||||
{"w": "day", "s": 0},
|
|
||||||
{"w": "delightful", "s": 0},
|
|
||||||
{"w": "desire", "s": 0},
|
|
||||||
{"w": "did", "s": 0},
|
|
||||||
{"w": "do", "s": 0},
|
|
||||||
{"w": "dream", "s": 0},
|
|
||||||
{"w": "e", "s": 1},
|
|
||||||
{"w": "eat", "s": 0},
|
|
||||||
{"w": "ed", "s": 1},
|
|
||||||
{"w": "er", "s": 1},
|
|
||||||
{"w": "es", "s": 1},
|
|
||||||
{"w": "est", "s": 1},
|
|
||||||
{"w": "evening", "s": 0},
|
|
||||||
{"w": "every", "s": 0},
|
|
||||||
{"w": "fall", "s": 0},
|
|
||||||
{"w": "favorite", "s": 0},
|
|
||||||
{"w": "feel", "s": 0},
|
|
||||||
{"w": "float", "s": 0},
|
|
||||||
{"w": "flower", "s": 0},
|
|
||||||
{"w": "for", "s": 0},
|
|
||||||
{"w": "from", "s": 0},
|
|
||||||
{"w": "full", "s": 0},
|
|
||||||
{"w": "fun", "s": 0},
|
|
||||||
{"w": "garden", "s": 0},
|
|
||||||
{"w": "get", "s": 0},
|
|
||||||
{"w": "ghost", "s": 0},
|
|
||||||
{"w": "good", "s": 0},
|
|
||||||
{"w": "grass", "s": 0},
|
|
||||||
{"w": "green", "s": 0},
|
|
||||||
{"w": "grow", "s": 0},
|
|
||||||
{"w": "happy", "s": 0},
|
|
||||||
{"w": "has", "s": 0},
|
|
||||||
{"w": "have", "s": 0},
|
|
||||||
{"w": "he", "s": 0},
|
|
||||||
{"w": "here", "s": 0},
|
|
||||||
{"w": "here", "s": 0},
|
|
||||||
{"w": "him", "s": 0},
|
|
||||||
{"w": "his", "s": 0},
|
|
||||||
{"w": "hot", "s": 0},
|
|
||||||
{"w": "house", "s": 0},
|
|
||||||
{"w": "how", "s": 0},
|
|
||||||
{"w": "I", "s": 0},
|
|
||||||
{"w": "I", "s": 0},
|
|
||||||
{"w": "if", "s": 0},
|
|
||||||
{"w": "in", "s": 0},
|
|
||||||
{"w": "ing", "s": 1},
|
|
||||||
{"w": "ing", "s": 1},
|
|
||||||
{"w": "is", "s": 0},
|
|
||||||
{"w": "is", "s": 0},
|
|
||||||
{"w": "it", "s": 0},
|
|
||||||
{"w": "keep", "s": 0},
|
|
||||||
{"w": "laugh", "s": 0},
|
|
||||||
{"w": "learn", "s": 0},
|
|
||||||
{"w": "leave", "s": 0},
|
|
||||||
{"w": "let", "s": 0},
|
|
||||||
{"w": "light", "s": 0},
|
|
||||||
{"w": "like", "s": 0},
|
|
||||||
{"w": "like", "s": 0},
|
|
||||||
{"w": "live", "s": 0},
|
|
||||||
{"w": "long", "s": 0},
|
|
||||||
{"w": "look", "s": 0},
|
|
||||||
{"w": "love", "s": 0},
|
|
||||||
{"w": "ly", "s": 1},
|
|
||||||
{"w": "magic", "s": 0},
|
|
||||||
{"w": "make", "s": 0},
|
|
||||||
{"w": "man", "s": 0},
|
|
||||||
{"w": "me", "s": 0},
|
|
||||||
{"w": "memory", "s": 0},
|
|
||||||
{"w": "month", "s": 0},
|
|
||||||
{"w": "more", "s": 0},
|
|
||||||
{"w": "morning", "s": 0},
|
|
||||||
{"w": "must", "s": 0},
|
|
||||||
{"w": "my", "s": 0},
|
|
||||||
{"w": "never", "s": 0},
|
|
||||||
{"w": "nibble", "s": 0},
|
|
||||||
{"w": "night", "s": 0},
|
|
||||||
{"w": "no", "s": 0},
|
|
||||||
{"w": "of", "s": 0},
|
|
||||||
{"w": "of", "s": 0},
|
|
||||||
{"w": "off", "s": 0},
|
|
||||||
{"w": "on", "s": 0},
|
|
||||||
{"w": "only", "s": 0},
|
|
||||||
{"w": "or", "s": 0},
|
|
||||||
{"w": "out", "s": 0},
|
|
||||||
{"w": "out", "s": 0},
|
|
||||||
{"w": "paint", "s": 0},
|
|
||||||
{"w": "people", "s": 0},
|
|
||||||
{"w": "perfect", "s": 0},
|
|
||||||
{"w": "play", "s": 0},
|
|
||||||
{"w": "proof", "s": 0},
|
|
||||||
{"w": "puff", "s": 0},
|
|
||||||
{"w": "r", "s": 1},
|
|
||||||
{"w": "rain", "s": 0},
|
|
||||||
{"w": "room", "s": 0},
|
|
||||||
{"w": "s", "s": 1},
|
|
||||||
{"w": "s", "s": 1},
|
|
||||||
{"w": "s", "s": 1},
|
|
||||||
{"w": "say", "s": 0},
|
|
||||||
{"w": "season", "s": 0},
|
|
||||||
{"w": "see", "s": 0},
|
|
||||||
{"w": "she", "s": 0},
|
|
||||||
{"w": "shine", "s": 0},
|
|
||||||
{"w": "simple", "s": 0},
|
|
||||||
{"w": "sky", "s": 0},
|
|
||||||
{"w": "snow", "s": 0},
|
|
||||||
{"w": "so", "s": 0},
|
|
||||||
{"w": "some", "s": 0},
|
|
||||||
{"w": "song", "s": 0},
|
|
||||||
{"w": "spring", "s": 0},
|
|
||||||
{"w": "summer", "s": 0},
|
|
||||||
{"w": "sun", "s": 0},
|
|
||||||
{"w": "sweet", "s": 0},
|
|
||||||
{"w": "take", "s": 0},
|
|
||||||
{"w": "talk", "s": 0},
|
|
||||||
{"w": "than", "s": 0},
|
|
||||||
{"w": "that", "s": 0},
|
|
||||||
{"w": "the", "s": 0},
|
|
||||||
{"w": "the", "s": 0},
|
|
||||||
{"w": "their", "s": 0},
|
|
||||||
{"w": "then", "s": 0},
|
|
||||||
{"w": "there", "s": 0},
|
|
||||||
{"w": "they", "s": 0},
|
|
||||||
{"w": "this", "s": 0},
|
|
||||||
{"w": "though", "s": 0},
|
|
||||||
{"w": "through", "s": 0},
|
|
||||||
{"w": "time", "s": 0},
|
|
||||||
{"w": "to", "s": 0},
|
|
||||||
{"w": "to", "s": 0},
|
|
||||||
{"w": "together", "s": 0},
|
|
||||||
{"w": "too", "s": 0},
|
|
||||||
{"w": "touch", "s": 0},
|
|
||||||
{"w": "trick", "s": 0},
|
|
||||||
{"w": "truth", "s": 0},
|
|
||||||
{"w": "up", "s": 0},
|
|
||||||
{"w": "us", "s": 0},
|
|
||||||
{"w": "use", "s": 0},
|
|
||||||
{"w": "vacation", "s": 0},
|
|
||||||
{"w": "walk", "s": 0},
|
|
||||||
{"w": "want", "s": 0},
|
|
||||||
{"w": "warm", "s": 0},
|
|
||||||
{"w": "was", "s": 0},
|
|
||||||
{"w": "watch", "s": 0},
|
|
||||||
{"w": "we", "s": 0},
|
|
||||||
{"w": "weather", "s": 0},
|
|
||||||
{"w": "were", "s": 0},
|
|
||||||
{"w": "when", "s": 0},
|
|
||||||
{"w": "which", "s": 0},
|
|
||||||
{"w": "whisper", "s": 0},
|
|
||||||
{"w": "who", "s": 0},
|
|
||||||
{"w": "why", "s": 0},
|
|
||||||
{"w": "will", "s": 0},
|
|
||||||
{"w": "winter", "s": 0},
|
|
||||||
{"w": "with", "s": 0},
|
|
||||||
{"w": "woman", "s": 0},
|
|
||||||
{"w": "word", "s": 0},
|
|
||||||
{"w": "work", "s": 0},
|
|
||||||
{"w": "world", "s": 0},
|
|
||||||
{"w": "would", "s": 0},
|
|
||||||
{"w": "y", "s": 1},
|
|
||||||
{"w": "year", "s": 0},
|
|
||||||
{"w": "you", "s": 0},
|
|
||||||
{"w": "you", "s": 0},
|
|
||||||
{"w": "your", "s": 0}
|
|
||||||
]
|
|
|
@ -0,0 +1,211 @@
|
||||||
|
export const words = [
|
||||||
|
{ w: "a", s: 0 },
|
||||||
|
{ w: "a", s: 0 },
|
||||||
|
{ w: "about", s: 0 },
|
||||||
|
{ w: "above", s: 0 },
|
||||||
|
{ w: "after", s: 0 },
|
||||||
|
{ w: "all", s: 0 },
|
||||||
|
{ w: "almost", s: 0 },
|
||||||
|
{ w: "always", s: 0 },
|
||||||
|
{ w: "am", s: 0 },
|
||||||
|
{ w: "an", s: 0 },
|
||||||
|
{ w: "an", s: 0 },
|
||||||
|
{ w: "and", s: 0 },
|
||||||
|
{ w: "and", s: 0 },
|
||||||
|
{ w: "animal", s: 0 },
|
||||||
|
{ w: "apple", s: 0 },
|
||||||
|
{ w: "are", s: 0 },
|
||||||
|
{ w: "as", s: 0 },
|
||||||
|
{ w: "as", s: 0 },
|
||||||
|
{ w: "ask", s: 0 },
|
||||||
|
{ w: "at", s: 0 },
|
||||||
|
{ w: "bad", s: 0 },
|
||||||
|
{ w: "be", s: 0 },
|
||||||
|
{ w: "beauty", s: 0 },
|
||||||
|
{ w: "believe", s: 0 },
|
||||||
|
{ w: "beneath", s: 0 },
|
||||||
|
{ w: "between", s: 0 },
|
||||||
|
{ w: "bird", s: 0 },
|
||||||
|
{ w: "birthday", s: 0 },
|
||||||
|
{ w: "blend", s: 0 },
|
||||||
|
{ w: "blue", s: 0 },
|
||||||
|
{ w: "bring", s: 0 },
|
||||||
|
{ w: "but", s: 0 },
|
||||||
|
{ w: "but", s: 0 },
|
||||||
|
{ w: "butterfly", s: 0 },
|
||||||
|
{ w: "by", s: 0 },
|
||||||
|
{ w: "calendar", s: 0 },
|
||||||
|
{ w: "can", s: 0 },
|
||||||
|
{ w: "celebrate", s: 0 },
|
||||||
|
{ w: "change", s: 0 },
|
||||||
|
{ w: "cloud", s: 0 },
|
||||||
|
{ w: "cold", s: 0 },
|
||||||
|
{ w: "come", s: 0 },
|
||||||
|
{ w: "comfort", s: 0 },
|
||||||
|
{ w: "could", s: 0 },
|
||||||
|
{ w: "d", s: 1 },
|
||||||
|
{ w: "dark", s: 0 },
|
||||||
|
{ w: "day", s: 0 },
|
||||||
|
{ w: "delightful", s: 0 },
|
||||||
|
{ w: "desire", s: 0 },
|
||||||
|
{ w: "did", s: 0 },
|
||||||
|
{ w: "do", s: 0 },
|
||||||
|
{ w: "dream", s: 0 },
|
||||||
|
{ w: "e", s: 1 },
|
||||||
|
{ w: "eat", s: 0 },
|
||||||
|
{ w: "ed", s: 1 },
|
||||||
|
{ w: "er", s: 1 },
|
||||||
|
{ w: "es", s: 1 },
|
||||||
|
{ w: "est", s: 1 },
|
||||||
|
{ w: "evening", s: 0 },
|
||||||
|
{ w: "every", s: 0 },
|
||||||
|
{ w: "fall", s: 0 },
|
||||||
|
{ w: "favorite", s: 0 },
|
||||||
|
{ w: "feel", s: 0 },
|
||||||
|
{ w: "float", s: 0 },
|
||||||
|
{ w: "flower", s: 0 },
|
||||||
|
{ w: "for", s: 0 },
|
||||||
|
{ w: "from", s: 0 },
|
||||||
|
{ w: "full", s: 0 },
|
||||||
|
{ w: "fun", s: 0 },
|
||||||
|
{ w: "garden", s: 0 },
|
||||||
|
{ w: "get", s: 0 },
|
||||||
|
{ w: "ghost", s: 0 },
|
||||||
|
{ w: "good", s: 0 },
|
||||||
|
{ w: "grass", s: 0 },
|
||||||
|
{ w: "green", s: 0 },
|
||||||
|
{ w: "grow", s: 0 },
|
||||||
|
{ w: "happy", s: 0 },
|
||||||
|
{ w: "has", s: 0 },
|
||||||
|
{ w: "have", s: 0 },
|
||||||
|
{ w: "he", s: 0 },
|
||||||
|
{ w: "here", s: 0 },
|
||||||
|
{ w: "here", s: 0 },
|
||||||
|
{ w: "him", s: 0 },
|
||||||
|
{ w: "his", s: 0 },
|
||||||
|
{ w: "hot", s: 0 },
|
||||||
|
{ w: "house", s: 0 },
|
||||||
|
{ w: "how", s: 0 },
|
||||||
|
{ w: "I", s: 0 },
|
||||||
|
{ w: "I", s: 0 },
|
||||||
|
{ w: "if", s: 0 },
|
||||||
|
{ w: "in", s: 0 },
|
||||||
|
{ w: "ing", s: 1 },
|
||||||
|
{ w: "ing", s: 1 },
|
||||||
|
{ w: "is", s: 0 },
|
||||||
|
{ w: "is", s: 0 },
|
||||||
|
{ w: "it", s: 0 },
|
||||||
|
{ w: "keep", s: 0 },
|
||||||
|
{ w: "laugh", s: 0 },
|
||||||
|
{ w: "learn", s: 0 },
|
||||||
|
{ w: "leave", s: 0 },
|
||||||
|
{ w: "let", s: 0 },
|
||||||
|
{ w: "light", s: 0 },
|
||||||
|
{ w: "like", s: 0 },
|
||||||
|
{ w: "like", s: 0 },
|
||||||
|
{ w: "live", s: 0 },
|
||||||
|
{ w: "long", s: 0 },
|
||||||
|
{ w: "look", s: 0 },
|
||||||
|
{ w: "love", s: 0 },
|
||||||
|
{ w: "ly", s: 1 },
|
||||||
|
{ w: "magic", s: 0 },
|
||||||
|
{ w: "make", s: 0 },
|
||||||
|
{ w: "man", s: 0 },
|
||||||
|
{ w: "me", s: 0 },
|
||||||
|
{ w: "memory", s: 0 },
|
||||||
|
{ w: "month", s: 0 },
|
||||||
|
{ w: "more", s: 0 },
|
||||||
|
{ w: "morning", s: 0 },
|
||||||
|
{ w: "must", s: 0 },
|
||||||
|
{ w: "my", s: 0 },
|
||||||
|
{ w: "never", s: 0 },
|
||||||
|
{ w: "nibble", s: 0 },
|
||||||
|
{ w: "night", s: 0 },
|
||||||
|
{ w: "no", s: 0 },
|
||||||
|
{ w: "of", s: 0 },
|
||||||
|
{ w: "of", s: 0 },
|
||||||
|
{ w: "off", s: 0 },
|
||||||
|
{ w: "on", s: 0 },
|
||||||
|
{ w: "only", s: 0 },
|
||||||
|
{ w: "or", s: 0 },
|
||||||
|
{ w: "out", s: 0 },
|
||||||
|
{ w: "out", s: 0 },
|
||||||
|
{ w: "paint", s: 0 },
|
||||||
|
{ w: "people", s: 0 },
|
||||||
|
{ w: "perfect", s: 0 },
|
||||||
|
{ w: "play", s: 0 },
|
||||||
|
{ w: "proof", s: 0 },
|
||||||
|
{ w: "puff", s: 0 },
|
||||||
|
{ w: "r", s: 1 },
|
||||||
|
{ w: "rain", s: 0 },
|
||||||
|
{ w: "room", s: 0 },
|
||||||
|
{ w: "s", s: 1 },
|
||||||
|
{ w: "s", s: 1 },
|
||||||
|
{ w: "s", s: 1 },
|
||||||
|
{ w: "say", s: 0 },
|
||||||
|
{ w: "season", s: 0 },
|
||||||
|
{ w: "see", s: 0 },
|
||||||
|
{ w: "she", s: 0 },
|
||||||
|
{ w: "shine", s: 0 },
|
||||||
|
{ w: "simple", s: 0 },
|
||||||
|
{ w: "sky", s: 0 },
|
||||||
|
{ w: "snow", s: 0 },
|
||||||
|
{ w: "so", s: 0 },
|
||||||
|
{ w: "some", s: 0 },
|
||||||
|
{ w: "song", s: 0 },
|
||||||
|
{ w: "spring", s: 0 },
|
||||||
|
{ w: "summer", s: 0 },
|
||||||
|
{ w: "sun", s: 0 },
|
||||||
|
{ w: "sweet", s: 0 },
|
||||||
|
{ w: "take", s: 0 },
|
||||||
|
{ w: "talk", s: 0 },
|
||||||
|
{ w: "than", s: 0 },
|
||||||
|
{ w: "that", s: 0 },
|
||||||
|
{ w: "the", s: 0 },
|
||||||
|
{ w: "the", s: 0 },
|
||||||
|
{ w: "their", s: 0 },
|
||||||
|
{ w: "then", s: 0 },
|
||||||
|
{ w: "there", s: 0 },
|
||||||
|
{ w: "they", s: 0 },
|
||||||
|
{ w: "this", s: 0 },
|
||||||
|
{ w: "though", s: 0 },
|
||||||
|
{ w: "through", s: 0 },
|
||||||
|
{ w: "time", s: 0 },
|
||||||
|
{ w: "to", s: 0 },
|
||||||
|
{ w: "to", s: 0 },
|
||||||
|
{ w: "together", s: 0 },
|
||||||
|
{ w: "too", s: 0 },
|
||||||
|
{ w: "touch", s: 0 },
|
||||||
|
{ w: "trick", s: 0 },
|
||||||
|
{ w: "truth", s: 0 },
|
||||||
|
{ w: "up", s: 0 },
|
||||||
|
{ w: "us", s: 0 },
|
||||||
|
{ w: "use", s: 0 },
|
||||||
|
{ w: "vacation", s: 0 },
|
||||||
|
{ w: "walk", s: 0 },
|
||||||
|
{ w: "want", s: 0 },
|
||||||
|
{ w: "warm", s: 0 },
|
||||||
|
{ w: "was", s: 0 },
|
||||||
|
{ w: "watch", s: 0 },
|
||||||
|
{ w: "we", s: 0 },
|
||||||
|
{ w: "weather", s: 0 },
|
||||||
|
{ w: "were", s: 0 },
|
||||||
|
{ w: "when", s: 0 },
|
||||||
|
{ w: "which", s: 0 },
|
||||||
|
{ w: "whisper", s: 0 },
|
||||||
|
{ w: "who", s: 0 },
|
||||||
|
{ w: "why", s: 0 },
|
||||||
|
{ w: "will", s: 0 },
|
||||||
|
{ w: "winter", s: 0 },
|
||||||
|
{ w: "with", s: 0 },
|
||||||
|
{ w: "woman", s: 0 },
|
||||||
|
{ w: "word", s: 0 },
|
||||||
|
{ w: "work", s: 0 },
|
||||||
|
{ w: "world", s: 0 },
|
||||||
|
{ w: "would", s: 0 },
|
||||||
|
{ w: "y", s: 1 },
|
||||||
|
{ w: "year", s: 0 },
|
||||||
|
{ w: "you", s: 0 },
|
||||||
|
{ w: "you", s: 0 },
|
||||||
|
{ w: "your", s: 0 },
|
||||||
|
];
|
|
@ -0,0 +1,61 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"paths": {
|
||||||
|
"@goauthentik/docs/*": ["../website/docs/*"]
|
||||||
|
},
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"target": "esnext",
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"lib": [
|
||||||
|
"ES5",
|
||||||
|
"ES2015",
|
||||||
|
"ES2016",
|
||||||
|
"ES2017",
|
||||||
|
"ES2018",
|
||||||
|
"ES2019",
|
||||||
|
"ES2020",
|
||||||
|
"ESNext",
|
||||||
|
"DOM",
|
||||||
|
"DOM.Iterable",
|
||||||
|
"WebWorker"
|
||||||
|
],
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"strictBindCallApply": true,
|
||||||
|
"strictFunctionTypes": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"allowUnreachableCode": false,
|
||||||
|
"allowUnusedLabels": false,
|
||||||
|
"useDefineForClassFields": false,
|
||||||
|
"alwaysStrict": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "ts-lit-plugin",
|
||||||
|
"strict": true,
|
||||||
|
"rules": {
|
||||||
|
"no-unknown-tag-name": "off",
|
||||||
|
"no-missing-import": "off",
|
||||||
|
"no-incompatible-type-binding": "off",
|
||||||
|
"no-unknown-property": "off",
|
||||||
|
"no-unknown-attribute": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@genesiscommunitysuccess/custom-elements-lsp",
|
||||||
|
"designSystemPrefix": "ak-",
|
||||||
|
"parser": {
|
||||||
|
"timeout": 2000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"paths": {
|
||||||
|
"@goauthentik/admin/*": ["./src/admin/*"],
|
||||||
|
"@goauthentik/common/*": ["./src/common/*"],
|
||||||
|
"@goauthentik/components/*": ["./src/components/*"],
|
||||||
|
"@goauthentik/docs/*": ["../website/docs/*"],
|
||||||
|
"@goauthentik/elements/*": ["./src/elements/*"],
|
||||||
|
"@goauthentik/flow/*": ["./src/flow/*"],
|
||||||
|
"@goauthentik/locales/*": ["./src/locales/*"],
|
||||||
|
"@goauthentik/polyfill/*": ["./src/polyfill/*"],
|
||||||
|
"@goauthentik/standalone/*": ["./src/standalone/*"],
|
||||||
|
"@goauthentik/user/*": ["./src/user/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue