Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a643383e79
|
||
|
|
2fca442922
|
||
|
|
e002427aef
|
||
|
|
eeff641a1d
|
||
|
|
96288b1eb8
|
||
|
|
8c19266760 | ||
|
|
89cdb00765 | ||
|
|
596246f20a | ||
|
|
475dd3379b
|
||
|
|
81492125f4 | ||
|
|
36d417a422 | ||
|
|
4987f5ee1a | ||
|
|
362b984753 | ||
|
|
ba93a54942 | ||
|
|
5fa54719b2 | ||
|
|
e224616b99 | ||
|
|
a72470459a | ||
|
|
80e0c405bc | ||
|
|
5c8ba2e11e | ||
|
|
85def8466c | ||
|
|
b5e606a839 | ||
|
|
8dff6a5ad0 | ||
|
|
b0cf2d7119 | ||
|
|
3e05458944 | ||
|
|
4d7955fb48 | ||
|
|
81d1f21236 | ||
|
|
aa406e623d | ||
|
|
5d6d4af8bd | ||
|
|
d944cb0afe | ||
|
|
9d6e0b982e | ||
|
|
167277b037 | ||
|
|
db8d90f579 | ||
|
|
16a72caa00 | ||
|
|
95c5f229f5 | ||
|
|
55adb79705 | ||
|
|
e97f0ea9b4 | ||
|
|
ff80ee516f | ||
|
|
6a5e54e130 | ||
|
|
ebc99c0f36 | ||
|
|
0eb3fcc3f6 | ||
|
|
1a56cd7603 | ||
|
|
7cdf209032
|
||
|
|
eecb582665
|
||
|
|
61b2bd0f1e
|
||
|
|
a635a5f55c | ||
|
|
63806f106c | ||
|
|
f41af1966e
|
||
|
|
7a6cab29db
|
||
|
|
b102a061fe
|
||
|
|
356282f61c
|
||
|
|
ce17728e0b
|
||
|
|
704c8d27c9
|
||
|
|
d20372fe33
|
||
|
|
e874b26bb9
|
||
|
|
08612d97e6
|
||
|
|
f98576f3ae
|
||
|
|
537f9864a0
|
||
|
|
59911eb929
|
||
|
|
8e64d32209
|
||
|
|
b5bc95bc2b
|
||
|
|
a699d467f1
|
||
|
|
8192ea71c4 | ||
|
|
8646792d46
|
||
|
|
0957c06a18 | ||
|
|
6f5ac3c0d2
|
||
|
|
b20c253383
|
||
|
|
76a035424d
|
||
|
|
34d5394b3c
|
||
|
|
bc399b43b6
|
||
|
|
051fb7c108
|
||
|
|
4079156776
|
||
|
|
4ed01d7d28
|
||
|
|
c720917627 | ||
|
|
2ce8c849c0 | ||
|
|
32112edbde
|
||
|
|
f28bf9ad06 | ||
|
|
1e1aafa27a
|
||
|
|
cd097d0e76
|
||
|
|
ea2f1eaa05
|
||
|
|
60c534d4a2
|
||
|
|
147b55c586 | ||
|
|
6e0ff0c596 | ||
|
|
21b0ad7461 | ||
|
|
6db91105da | ||
|
|
cc8e861b33 | ||
|
|
d127309a13 | ||
|
|
b592524204 | ||
|
|
5c89f10e9f
|
||
|
|
cfd6e630a3
|
||
|
|
ac43ae3669
|
||
|
|
5554489d70
|
||
|
|
ae6ab88e19
|
||
|
|
a0cbcc7e71
|
||
|
|
679731d3e2
|
||
|
|
96d3c138f6
|
||
|
|
6648d1d7d5
|
||
|
|
9c391c8c05
|
||
|
|
77b83f5d21
|
||
|
|
8c9c581cc0
|
||
|
|
46090e9159
|
||
|
|
fd8e12f925
|
||
|
|
b73e92a8a4
|
||
|
|
a3f7f90fdc
|
||
|
|
a39caf1a34
|
||
|
|
38c1f78d4f
|
||
|
|
5cbe055c34
|
||
|
|
2f208aa6e4
|
||
|
|
c6202d2ac6
|
||
|
|
a3f666c974
|
||
|
|
4a7045a1cb
|
||
|
|
a048d395f7
|
||
|
|
9398321fac
|
||
|
|
d348174f95
|
||
|
|
d0d8222e40
|
||
|
|
ed2bda5c02
|
||
|
|
96d829accb
|
||
|
|
67951fa031
|
||
|
|
c98a51795d
|
||
|
|
95d78c22eb
|
||
|
|
05f5c59721
|
||
|
|
a498dc59d7
|
||
|
|
4a2087d4e3
|
||
|
|
5e75ace58d
|
||
|
|
562109f207
|
||
|
|
f5c715f5c0
|
||
|
|
496744471a
|
||
|
|
4675809911
|
||
|
|
f64c535971
|
||
|
|
79e9e08464
|
||
|
|
7ef8b3a585
|
||
|
|
1be3ad0410
|
||
|
|
36f82a9a76
|
||
|
|
0db10a04d8
|
||
|
|
3003a68e39
|
||
|
|
266137ff5f
|
||
|
|
cd5a3c6868
|
||
|
|
785c2ccaf7
|
||
|
|
6c4ac504da
|
||
|
|
26db9e76e1
|
||
|
|
796bc223af
|
||
|
|
626f1b7f37
|
||
|
|
1eeb5a6820
|
||
|
|
5f4fbd5e05
|
||
|
|
1bc0224f76
|
||
|
|
ce5e53c71f
|
||
|
|
615412c880
|
||
|
|
c2b49e66d9
|
||
|
|
ef13d4a6e6
|
||
|
|
1ec59b0c07
|
||
|
|
e1023b5d50
|
||
|
|
4bbd58b361
|
||
|
|
5a66284bb5
|
||
|
|
0b276e7b32
|
||
|
|
e51f2133ef
|
||
|
|
4b23bff789
|
||
|
|
e44b95db2e
|
||
|
|
bb4cee97a8
|
||
|
|
826d5e8c7d
|
||
|
|
9678fc9a3c
|
||
|
|
e4e6f15e9a
|
||
|
|
bfed3bee15
|
||
|
|
96edc6e538
|
||
|
|
33e78ff8a5
|
||
|
|
f3d3562cec
|
||
|
|
d8a0016dba
|
No files matched your search
@@ -38,6 +38,7 @@ temp/
|
|||||||
# Editor and IDE state
|
# Editor and IDE state
|
||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
|
.frontmatter/
|
||||||
*.code-workspace
|
*.code-workspace
|
||||||
|
|
||||||
# Local AI-agent state
|
# Local AI-agent state
|
||||||
@@ -47,11 +48,16 @@ temp/
|
|||||||
.cursor/
|
.cursor/
|
||||||
.continue/
|
.continue/
|
||||||
.windsurf/
|
.windsurf/
|
||||||
|
/.playwright-mcp/
|
||||||
|
|
||||||
|
# Local visual-comparison captures
|
||||||
|
/live-*.png
|
||||||
|
/live-*-snapshot.md
|
||||||
|
|
||||||
# Operating-system metadata
|
# Operating-system metadata
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
Desktop.ini
|
Desktop.ini
|
||||||
|
|
||||||
# Read-only wget mirror used as migration source material
|
# Private portal collection, mappings and ciphertext; never a public asset source.
|
||||||
/www.azinstitute4autism.com/
|
/www/private/employee-portal/
|
||||||
@@ -1,34 +1,43 @@
|
|||||||
# AGENTS.md
|
# AGENTS.md
|
||||||
|
|
||||||
|
## Instruction precedence
|
||||||
|
|
||||||
|
This file is the authoritative project policy for all editors and AI agents.
|
||||||
|
`MIGRATION_BRIEF.md` supplies detailed implementation guidance and must be read
|
||||||
|
with this file. If the two files appear to conflict, follow `AGENTS.md` and
|
||||||
|
update the conflicting documentation before continuing.
|
||||||
|
|
||||||
|
The `migration-foundation` branch is a reference and recovery point only. It is
|
||||||
|
not the visual or architectural source of truth. Reuse its nonvisual tooling or
|
||||||
|
content only after verifying it against the live public site.
|
||||||
|
|
||||||
## Project
|
## Project
|
||||||
|
|
||||||
This repository contains a mirrored copy of the Arizona Institute for Autism website.
|
|
||||||
|
|
||||||
Raw mirror source:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
./www.azinstitute4autism.com
|
|
||||||
```
|
|
||||||
|
|
||||||
New Astro site target:
|
New Astro site target:
|
||||||
|
|
||||||
```txt
|
```txt
|
||||||
./www
|
./www
|
||||||
```
|
```
|
||||||
|
|
||||||
The mirror was created with:
|
The live public website is the single source of truth:
|
||||||
|
|
||||||
```sh
|
```txt
|
||||||
wget --mirror --page-requisites --adjust-extension --convert-links --no-parent https://www.azinstitute4autism.com
|
https://www.azinstitute4autism.com
|
||||||
```
|
```
|
||||||
|
|
||||||
Do not modify, move, rename, or delete `./www.azinstitute4autism.com`.
|
Verify content, design, URLs, metadata, assets, and responsive behavior against
|
||||||
|
the live public site before treating them as authoritative.
|
||||||
Treat the mirror as read-only raw source material.
|
|
||||||
|
|
||||||
## Primary goal
|
## Primary goal
|
||||||
|
|
||||||
Create a clean, maintainable Astro + TypeScript site in `./www` that reproduces the current public site as faithfully as practical while replacing HubSpot-generated structure with a future-friendly static site architecture.
|
Create a clean, maintainable Astro + TypeScript site in `./www` that faithfully
|
||||||
|
reproduces the current public site's content, visual design, page structure, and
|
||||||
|
responsive behavior while replacing HubSpot-generated implementation details
|
||||||
|
with a future-friendly static site architecture.
|
||||||
|
|
||||||
|
This is a fidelity-first migration, not a redesign. The live public site is the
|
||||||
|
source of truth for both content and presentation. Maintainability must improve
|
||||||
|
the implementation without materially changing what visitors see.
|
||||||
|
|
||||||
The new site should be suitable for:
|
The new site should be suitable for:
|
||||||
|
|
||||||
@@ -149,13 +158,31 @@ Do not invent large Arabic or Spanish translations unless source content is avai
|
|||||||
|
|
||||||
## Design rules
|
## Design rules
|
||||||
|
|
||||||
The new design should be mostly faithful to the current site while becoming more maintainable.
|
The new site must remain visually faithful to the live public site. Do not
|
||||||
|
reinterpret, modernize, simplify, or genericize the design unless a specific
|
||||||
|
change is required for accessibility or reliable responsive behavior.
|
||||||
|
|
||||||
|
Preserve each source page's:
|
||||||
|
|
||||||
|
- section order and visible content
|
||||||
|
- typography, font weights, and text hierarchy
|
||||||
|
- colors, backgrounds, and decorative treatments
|
||||||
|
- container widths, spacing, alignment, and proportions
|
||||||
|
- imagery, crops, icons, borders, radii, and shadows
|
||||||
|
- header, navigation, footer, calls to action, and forms
|
||||||
|
- desktop and mobile responsive behavior
|
||||||
|
|
||||||
|
Reuse exact source assets and locally available fonts wherever practical.
|
||||||
|
Do not replace distinctive source sections with generic cards, generic heroes,
|
||||||
|
or a new site-wide layout merely to make component reuse easier.
|
||||||
|
|
||||||
Use plain CSS with design tokens.
|
Use plain CSS with design tokens.
|
||||||
|
|
||||||
Create reusable Astro components for repeated layout sections.
|
Create reusable Astro components for repeated layout sections.
|
||||||
|
|
||||||
Do not create a messy page-by-page clone of HubSpot output.
|
Component boundaries must follow genuinely repeated source patterns. Distinctive
|
||||||
|
pages and sections may retain page-specific markup and CSS when abstraction
|
||||||
|
would reduce fidelity. Do not copy HubSpot's generated DOM or CSS wholesale.
|
||||||
|
|
||||||
Favor:
|
Favor:
|
||||||
|
|
||||||
@@ -163,10 +190,30 @@ Favor:
|
|||||||
- reusable components
|
- reusable components
|
||||||
- organized CSS
|
- organized CSS
|
||||||
- clear data files
|
- clear data files
|
||||||
- consistent spacing
|
- source-faithful spacing
|
||||||
- accessible markup
|
- accessible markup
|
||||||
- predictable file structure
|
- predictable file structure
|
||||||
|
|
||||||
|
### Visual validation
|
||||||
|
|
||||||
|
Before implementing a page family or shared component:
|
||||||
|
|
||||||
|
1. Inspect the live page and its HTML, CSS, assets, and responsive behavior.
|
||||||
|
2. Resolve any discrepancy in favor of the live public site.
|
||||||
|
3. Record the source structure and important visual details.
|
||||||
|
4. Identify which patterns are truly shared and which are page-specific.
|
||||||
|
|
||||||
|
After implementation, compare the Astro output with the live public site at
|
||||||
|
representative desktop and mobile viewport widths. Check the full page, not
|
||||||
|
only the header or first viewport. Fix material differences before treating the
|
||||||
|
page as complete.
|
||||||
|
|
||||||
|
Document intentional visual deviations and their reasons in:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
reports/migration-summary.md
|
||||||
|
```
|
||||||
|
|
||||||
## URL and SEO rules
|
## URL and SEO rules
|
||||||
|
|
||||||
Preserve existing public URL paths whenever possible.
|
Preserve existing public URL paths whenever possible.
|
||||||
@@ -294,9 +341,10 @@ reports/migration-summary.md
|
|||||||
|
|
||||||
Before large filesystem changes:
|
Before large filesystem changes:
|
||||||
|
|
||||||
1. Inspect the mirror.
|
1. Inspect the live public site.
|
||||||
2. Summarize the implementation plan.
|
2. Inspect the relevant source HTML, CSS, and assets in detail.
|
||||||
3. Proceed methodically.
|
3. Summarize the implementation and visual-validation plan.
|
||||||
|
4. Proceed methodically.
|
||||||
|
|
||||||
At the end of a task, summarize:
|
At the end of a task, summarize:
|
||||||
|
|
||||||
@@ -307,4 +355,7 @@ At the end of a task, summarize:
|
|||||||
- audit results
|
- audit results
|
||||||
- unresolved issues
|
- unresolved issues
|
||||||
|
|
||||||
Prioritize a clean, maintainable foundation over a pixel-perfect but messy clone.
|
Prioritize visual fidelity and content correctness while implementing them with
|
||||||
|
clean, maintainable Astro code. Do not trade away visible source design for a
|
||||||
|
generic foundation, and do not copy HubSpot-generated implementation merely to
|
||||||
|
obtain fidelity.
|
||||||
@@ -23,6 +23,7 @@ workspace even when Codex cannot discover its Git metadata.
|
|||||||
The profile:
|
The profile:
|
||||||
|
|
||||||
- never pauses for approval
|
- never pauses for approval
|
||||||
|
- automatically permits configured Playwright and OpenAI documentation MCP tools
|
||||||
- limits filesystem access to minimal runtime files plus this project
|
- limits filesystem access to minimal runtime files plus this project
|
||||||
- allows writes in this project
|
- allows writes in this project
|
||||||
- keeps `.codex` and `www.azinstitute4autism.com` read-only
|
- keeps `.codex` and `www.azinstitute4autism.com` read-only
|
||||||
@@ -42,6 +43,27 @@ The profile is stored outside the workspace at:
|
|||||||
Do not add `--sandbox`; legacy sandbox flags override the custom permission
|
Do not add `--sandbox`; legacy sandbox flags override the custom permission
|
||||||
profile. Do not use `--dangerously-bypass-approvals-and-sandbox`.
|
profile. Do not use `--dangerously-bypass-approvals-and-sandbox`.
|
||||||
|
|
||||||
|
The launcher also applies these session overrides:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
approval_policy="never"
|
||||||
|
mcp_servers.playwright.default_tools_approval_mode="approve"
|
||||||
|
mcp_servers.playwright.tool_timeout_sec=300
|
||||||
|
mcp_servers.openaiDeveloperDocs.default_tools_approval_mode="approve"
|
||||||
|
mcp_servers.openaiDeveloperDocs.tool_timeout_sec=300
|
||||||
|
```
|
||||||
|
|
||||||
|
The server-level `approve` settings pre-approve MCP tools without writing
|
||||||
|
incompatible per-tool approval tables into the profile. These settings allow
|
||||||
|
MCP tools to run without approval prompts and give
|
||||||
|
long-running browser operations up to five minutes. An MCP server may still
|
||||||
|
fail or time out. MCP elicitations that inherently require user input are
|
||||||
|
rejected rather than shown as unattended prompts.
|
||||||
|
|
||||||
|
Do not persist an individual MCP tool approval when prompted by an older or
|
||||||
|
misconfigured session. Codex CLI 0.133.0 may write a per-tool table that the
|
||||||
|
profile-v2 parser rejects on the next launch.
|
||||||
|
|
||||||
## Verification
|
## Verification
|
||||||
|
|
||||||
The nested setup smoke test confirmed that the project is readable and
|
The nested setup smoke test confirmed that the project is readable and
|
||||||
@@ -60,6 +82,35 @@ sandboxed command-line tools may fail HTTPS certificate verification. Do not
|
|||||||
disable certificate verification for package installation or sensitive
|
disable certificate verification for package installation or sensitive
|
||||||
retrievals; use live web search or review the retrieval from the host instead.
|
retrievals; use live web search or review the retrieval from the host instead.
|
||||||
|
|
||||||
|
## Optional local browser environment
|
||||||
|
|
||||||
|
The repository now provides a Linux-only `browser` development shell, separate
|
||||||
|
from the default content-editing shell. In a normal host terminal, from the
|
||||||
|
repository root:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
nix --extra-experimental-features "nix-command flakes" develop .#browser
|
||||||
|
```
|
||||||
|
|
||||||
|
It supplies a lockfile-matched Playwright driver, Chromium, headless shell and
|
||||||
|
font configuration. New processes launched inside this shell inherit
|
||||||
|
`PLAYWRIGHT_MODULE` and `PLAYWRIGHT_BROWSERS_PATH`; existing sessions do not.
|
||||||
|
For a guarded real-page smoke test, run from `www`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
TMPDIR=/tmp npm run build
|
||||||
|
TMPDIR=/tmp node tools/playwright-smoke.mjs
|
||||||
|
```
|
||||||
|
|
||||||
|
Preview and browser run in the same process environment, avoiding dependence on
|
||||||
|
remote MCP transport or cross-execution loopback access. All non-loopback and
|
||||||
|
non-GET/HEAD browser requests are blocked. Results/screenshots stay in `/tmp`.
|
||||||
|
The tool does not grant access to files or sockets denied by the autonomous
|
||||||
|
profile. If Nix realization, Chromium startup or loopback access is denied, keep
|
||||||
|
that result as an environment limitation and use the normal host terminal;
|
||||||
|
do not weaken TLS, identity, signing or sandbox configuration. No global MCP
|
||||||
|
configuration or launcher permissions were changed for this setup.
|
||||||
|
|
||||||
## Git limitation
|
## Git limitation
|
||||||
|
|
||||||
Codex always protects `.git` recursively in its `workspace-write` sandbox.
|
Codex always protects `.git` recursively in its `workspace-write` sandbox.
|
||||||
@@ -79,3 +130,9 @@ Check the startup banner before assigning work. It should report:
|
|||||||
approval: never
|
approval: never
|
||||||
sandbox: workspace-write ... (network access enabled)
|
sandbox: workspace-write ... (network access enabled)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Then check the MCP configuration:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
/mcp verbose
|
||||||
|
```
|
||||||
@@ -36,19 +36,24 @@ For the migration task:
|
|||||||
|
|
||||||
1. Read `AGENTS.md`.
|
1. Read `AGENTS.md`.
|
||||||
2. Read `MIGRATION_BRIEF.md`.
|
2. Read `MIGRATION_BRIEF.md`.
|
||||||
3. Inspect the local mirror.
|
3. Inspect the live public site as the single source of truth.
|
||||||
4. Provide a concise implementation plan.
|
4. Inspect the local mirror as a convenient extraction aid and identify
|
||||||
5. Scaffold or update the Astro project in `./www`.
|
relevant discrepancies.
|
||||||
6. Build extraction, crawl, audit, sitemap, and redirect tools.
|
5. Inspect the relevant live HTML, CSS, assets, and responsive behavior.
|
||||||
7. Extract content, media, metadata, navigation, and forms.
|
6. Provide a concise implementation and visual-validation plan.
|
||||||
8. Refactor into reusable Astro components.
|
7. Scaffold or update the Astro project in `./www`.
|
||||||
9. Configure Astro content collections and Front Matter CMS.
|
8. Build extraction, crawl, audit, sitemap, and redirect tools.
|
||||||
10. Add English, Arabic, and Spanish structure.
|
9. Extract content, media, metadata, navigation, and forms.
|
||||||
11. Add RTL CSS for Arabic.
|
10. Refactor genuinely repeated patterns into reusable Astro components.
|
||||||
12. Add SEO, sitemap, robots, redirects, and reports.
|
11. Compare rendered pages with the live site at desktop and mobile widths.
|
||||||
13. Run build and audits.
|
12. Configure Astro content collections and Front Matter CMS.
|
||||||
14. Fix practical errors.
|
13. Add English, Arabic, and Spanish structure.
|
||||||
15. Document incomplete items in `reports/migration-summary.md`.
|
14. Add RTL CSS for Arabic.
|
||||||
|
15. Add SEO, sitemap, robots, redirects, and reports.
|
||||||
|
16. Run build and audits.
|
||||||
|
17. Fix practical errors.
|
||||||
|
18. Document incomplete items and intentional visual deviations in
|
||||||
|
`reports/migration-summary.md`.
|
||||||
|
|
||||||
## Important Claude-specific behavior
|
## Important Claude-specific behavior
|
||||||
|
|
||||||
@@ -60,6 +65,15 @@ When asked to begin migration, first read the relevant project files, then work
|
|||||||
|
|
||||||
Treat `./www.azinstitute4autism.com` as read-only source material.
|
Treat `./www.azinstitute4autism.com` as read-only source material.
|
||||||
|
|
||||||
|
Treat the live public site as the single content and visual source of truth.
|
||||||
|
Use the mirror only as a convenient local extraction and inspection aid. When
|
||||||
|
the live site and mirror disagree, follow the live site and document material
|
||||||
|
discrepancies. This migration is not a redesign. Do not substitute a generic
|
||||||
|
visual system for the live design.
|
||||||
|
|
||||||
|
Treat the `migration-foundation` branch as reference-only. Do not use it as the
|
||||||
|
visual baseline.
|
||||||
|
|
||||||
Do not delete or overwrite raw source assets.
|
Do not delete or overwrite raw source assets.
|
||||||
|
|
||||||
Do not remove files unless you are certain they are generated output in `./www`.
|
Do not remove files unless you are certain they are generated output in `./www`.
|
||||||
@@ -105,4 +119,4 @@ When finished, report:
|
|||||||
- redirect status
|
- redirect status
|
||||||
- build status
|
- build status
|
||||||
- audit status
|
- audit status
|
||||||
- manual review items
|
- manual review items
|
||||||
@@ -1,14 +1,21 @@
|
|||||||
# MIGRATION_BRIEF.md
|
# MIGRATION_BRIEF.md
|
||||||
|
|
||||||
|
## Authority and interpretation
|
||||||
|
|
||||||
|
Read and follow `AGENTS.md` first. It is the authoritative project policy; this
|
||||||
|
brief provides detailed implementation guidance. If any wording here appears
|
||||||
|
to conflict with `AGENTS.md`, follow `AGENTS.md` and correct this brief before
|
||||||
|
continuing.
|
||||||
|
|
||||||
|
This is a fidelity-first migration, not a redesign. The live public site is the
|
||||||
|
single content and visual source of truth. The `migration-foundation` branch
|
||||||
|
may be consulted for nonvisual tooling and recovered content, but it must not
|
||||||
|
be used as the visual baseline.
|
||||||
|
|
||||||
## Task
|
## Task
|
||||||
|
|
||||||
Migrate the public Arizona Institute for Autism website from a mirrored HubSpot-generated static copy into a clean, maintainable Astro + TypeScript site.
|
Migrate the public Arizona Institute for Autism website into a clean,
|
||||||
|
maintainable Astro + TypeScript site.
|
||||||
Raw mirrored source:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
./www.azinstitute4autism.com
|
|
||||||
```
|
|
||||||
|
|
||||||
New Astro project target:
|
New Astro project target:
|
||||||
|
|
||||||
@@ -16,23 +23,17 @@ New Astro project target:
|
|||||||
./www
|
./www
|
||||||
```
|
```
|
||||||
|
|
||||||
The raw mirror was created with:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
wget --mirror --page-requisites --adjust-extension --convert-links --no-parent https://www.azinstitute4autism.com
|
|
||||||
```
|
|
||||||
|
|
||||||
There is no HubSpot export, no HubSpot CLI access, and no HubSpot API access.
|
There is no HubSpot export, no HubSpot CLI access, and no HubSpot API access.
|
||||||
|
|
||||||
Use the local mirror as the primary source.
|
Use the live public website as the single source of truth:
|
||||||
|
|
||||||
If internet access is available, also crawl the live public website to catch missing URLs or assets:
|
|
||||||
|
|
||||||
```txt
|
```txt
|
||||||
https://www.azinstitute4autism.com
|
https://www.azinstitute4autism.com
|
||||||
```
|
```
|
||||||
|
|
||||||
The live crawl is only for comparison and gap-filling. The local mirror remains the source of truth.
|
Verify content, design, URLs, metadata, assets, and responsive behavior against
|
||||||
|
the live site. If the live site is temporarily unavailable, work that depends
|
||||||
|
on its authority must remain explicitly unverified.
|
||||||
|
|
||||||
## High-level goal
|
## High-level goal
|
||||||
|
|
||||||
@@ -49,7 +50,7 @@ Create a new Astro site in `./www` that is:
|
|||||||
- SEO-conscious
|
- SEO-conscious
|
||||||
- accessible
|
- accessible
|
||||||
- maintainable
|
- maintainable
|
||||||
- mostly faithful to the current design
|
- faithful to the current design, layout, and responsive behavior
|
||||||
- free of unnecessary HubSpot-generated structure
|
- free of unnecessary HubSpot-generated structure
|
||||||
- prepared for future development and publishing
|
- prepared for future development and publishing
|
||||||
|
|
||||||
@@ -57,10 +58,6 @@ Create a new Astro site in `./www` that is:
|
|||||||
|
|
||||||
Do not modify or delete:
|
Do not modify or delete:
|
||||||
|
|
||||||
```txt
|
|
||||||
./www.azinstitute4autism.com
|
|
||||||
```
|
|
||||||
|
|
||||||
Do not depend on:
|
Do not depend on:
|
||||||
|
|
||||||
- HubSpot CLI
|
- HubSpot CLI
|
||||||
@@ -238,11 +235,20 @@ Do not invent extensive translations.
|
|||||||
|
|
||||||
### Design direction
|
### Design direction
|
||||||
|
|
||||||
The new site should be more maintainable while remaining mostly faithful to the current design.
|
The new site must faithfully reproduce the live public site's visible design,
|
||||||
|
layout, content hierarchy, and responsive behavior. Do not redesign,
|
||||||
|
reinterpret, modernize, simplify, or genericize source pages.
|
||||||
|
|
||||||
Do not create a messy clone of HubSpot output.
|
Preserve typography, colors, backgrounds, spacing, proportions, imagery,
|
||||||
|
section order, decorative treatments, navigation, calls to action, forms, and
|
||||||
|
page-specific distinctions. Reuse exact source assets and fonts wherever
|
||||||
|
practical.
|
||||||
|
|
||||||
Refactor repeated sections into reusable components.
|
Do not copy HubSpot-generated DOM or CSS wholesale.
|
||||||
|
|
||||||
|
Refactor genuinely repeated sections into reusable components. Do not force
|
||||||
|
distinctive source sections into generic components when doing so changes their
|
||||||
|
appearance or structure.
|
||||||
|
|
||||||
Use semantic markup.
|
Use semantic markup.
|
||||||
|
|
||||||
@@ -250,6 +256,21 @@ Use plain CSS with design tokens.
|
|||||||
|
|
||||||
Do not use Tailwind.
|
Do not use Tailwind.
|
||||||
|
|
||||||
|
### Fidelity workflow
|
||||||
|
|
||||||
|
For each page family or shared component:
|
||||||
|
|
||||||
|
1. Inspect the live page, HTML, CSS, assets, and responsive behavior.
|
||||||
|
2. Resolve discrepancies in favor of the live public site.
|
||||||
|
3. Record important visual details and identify genuine shared patterns.
|
||||||
|
4. Implement the page or component in maintainable Astro code.
|
||||||
|
5. Compare the complete rendered page with the live public site at representative
|
||||||
|
desktop and mobile viewport widths.
|
||||||
|
6. Fix material visual differences before marking it complete.
|
||||||
|
|
||||||
|
Document intentional visual deviations and their reasons in
|
||||||
|
`reports/migration-summary.md`.
|
||||||
|
|
||||||
## Required target structure
|
## Required target structure
|
||||||
|
|
||||||
Create this project structure in `./www`:
|
Create this project structure in `./www`:
|
||||||
@@ -362,15 +383,11 @@ www/
|
|||||||
rtl.css
|
rtl.css
|
||||||
|
|
||||||
tools/
|
tools/
|
||||||
extract-site.mjs
|
|
||||||
crawl-live-site.mjs
|
crawl-live-site.mjs
|
||||||
audit-links.mjs
|
audit-links.mjs
|
||||||
generate-redirects.mjs
|
generate-redirects.mjs
|
||||||
generate-sitemap.mjs
|
generate-sitemap.mjs
|
||||||
|
|
||||||
original/
|
|
||||||
README.md
|
|
||||||
|
|
||||||
reports/
|
reports/
|
||||||
url-inventory.csv
|
url-inventory.csv
|
||||||
asset-inventory.csv
|
asset-inventory.csv
|
||||||
@@ -383,17 +400,10 @@ www/
|
|||||||
migration-summary.md
|
migration-summary.md
|
||||||
```
|
```
|
||||||
|
|
||||||
The `original/README.md` file should explain that the raw source mirror lives outside the Astro project at:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
../www.azinstitute4autism.com
|
|
||||||
```
|
|
||||||
|
|
||||||
Do not duplicate the entire raw mirror inside `./www` unless there is a specific reason.
|
|
||||||
|
|
||||||
## Initial inspection
|
## Initial inspection
|
||||||
|
|
||||||
Before creating or modifying files, inspect the mirror.
|
Before creating or modifying files, inspect the live public site and current
|
||||||
|
project sources.
|
||||||
|
|
||||||
Look for:
|
Look for:
|
||||||
|
|
||||||
@@ -643,7 +653,9 @@ src/styles/rtl.css
|
|||||||
src/styles/global.css
|
src/styles/global.css
|
||||||
```
|
```
|
||||||
|
|
||||||
Infer the current site’s visual language from the mirrored CSS and HTML.
|
Extract the current site's visual language from the live public site. Tokens
|
||||||
|
must encode source values; they must not introduce a replacement visual
|
||||||
|
system.
|
||||||
|
|
||||||
Use design tokens such as:
|
Use design tokens such as:
|
||||||
|
|
||||||
@@ -790,7 +802,7 @@ It should support:
|
|||||||
- hreflang links where translation relationships are known
|
- hreflang links where translation relationships are known
|
||||||
- JSON-LD/schema when extracted
|
- JSON-LD/schema when extracted
|
||||||
|
|
||||||
Preserve metadata from the mirror wherever possible.
|
Preserve metadata from the live site wherever possible.
|
||||||
|
|
||||||
Generate:
|
Generate:
|
||||||
|
|
||||||
@@ -843,49 +855,27 @@ src/pages/es/[...slug].astro
|
|||||||
|
|
||||||
Implement routing in a clean, maintainable way.
|
Implement routing in a clean, maintainable way.
|
||||||
|
|
||||||
## Extraction scripts
|
## Migration utility scripts
|
||||||
|
|
||||||
Create:
|
Create:
|
||||||
|
|
||||||
```txt
|
```txt
|
||||||
tools/extract-site.mjs
|
|
||||||
tools/crawl-live-site.mjs
|
tools/crawl-live-site.mjs
|
||||||
tools/audit-links.mjs
|
tools/audit-links.mjs
|
||||||
tools/generate-redirects.mjs
|
tools/generate-redirects.mjs
|
||||||
tools/generate-sitemap.mjs
|
tools/generate-sitemap.mjs
|
||||||
```
|
```
|
||||||
|
|
||||||
### `extract-site.mjs`
|
|
||||||
|
|
||||||
This script should:
|
|
||||||
|
|
||||||
- scan the mirror
|
|
||||||
- identify HTML files
|
|
||||||
- infer URL paths
|
|
||||||
- identify page type
|
|
||||||
- identify blog posts
|
|
||||||
- identify listing pages
|
|
||||||
- extract title/meta/H1/body/main content
|
|
||||||
- extract canonical and Open Graph metadata
|
|
||||||
- extract schema/JSON-LD where present
|
|
||||||
- convert blog posts to Markdown where feasible
|
|
||||||
- create page Markdown where useful
|
|
||||||
- copy/normalize assets
|
|
||||||
- rewrite local asset references
|
|
||||||
- create initial reports
|
|
||||||
|
|
||||||
The script does not need to be perfect, but it should be useful and documented.
|
|
||||||
|
|
||||||
### `crawl-live-site.mjs`
|
### `crawl-live-site.mjs`
|
||||||
|
|
||||||
This script should:
|
This script should:
|
||||||
|
|
||||||
- attempt to crawl the live site if internet is available
|
- attempt to crawl the live site if internet is available
|
||||||
- discover URLs from sitemap and internal links
|
- discover URLs from sitemap and internal links
|
||||||
- compare discovered URLs against the mirror
|
- compare discovered URLs against the current site source and generated output
|
||||||
- document missing URLs/assets
|
- document missing URLs/assets
|
||||||
- avoid aggressive crawling
|
- avoid aggressive crawling
|
||||||
- respect the scope of `www.azinstitute4autism.com`
|
- respect the live-site scope
|
||||||
|
|
||||||
### `audit-links.mjs`
|
### `audit-links.mjs`
|
||||||
|
|
||||||
@@ -1096,7 +1086,6 @@ Add scripts similar to:
|
|||||||
"dev": "astro dev",
|
"dev": "astro dev",
|
||||||
"build": "astro check && astro build",
|
"build": "astro check && astro build",
|
||||||
"preview": "astro preview",
|
"preview": "astro preview",
|
||||||
"extract": "node tools/extract-site.mjs",
|
|
||||||
"crawl": "node tools/crawl-live-site.mjs",
|
"crawl": "node tools/crawl-live-site.mjs",
|
||||||
"audit:links": "node tools/audit-links.mjs",
|
"audit:links": "node tools/audit-links.mjs",
|
||||||
"generate:sitemap": "node tools/generate-sitemap.mjs",
|
"generate:sitemap": "node tools/generate-sitemap.mjs",
|
||||||
@@ -1118,7 +1107,6 @@ www/README.md
|
|||||||
It should explain:
|
It should explain:
|
||||||
|
|
||||||
- what the project is
|
- what the project is
|
||||||
- where the raw mirror lives
|
|
||||||
- how the migration was performed
|
- how the migration was performed
|
||||||
- how to install dependencies
|
- how to install dependencies
|
||||||
- how to run local dev
|
- how to run local dev
|
||||||
@@ -1168,6 +1156,8 @@ The migration is successful when:
|
|||||||
- `./www` contains a working Astro project
|
- `./www` contains a working Astro project
|
||||||
- `npm run build` succeeds or unresolved build issues are clearly documented
|
- `npm run build` succeeds or unresolved build issues are clearly documented
|
||||||
- main public pages render locally
|
- main public pages render locally
|
||||||
|
- main public pages have been compared with the live site at desktop and mobile
|
||||||
|
widths and have no undocumented material visual differences
|
||||||
- blog posts are migrated into file-based Markdown where feasible
|
- blog posts are migrated into file-based Markdown where feasible
|
||||||
- Markdown files work with Front Matter CMS
|
- Markdown files work with Front Matter CMS
|
||||||
- MDX is available but not overused
|
- MDX is available but not overused
|
||||||
@@ -1191,18 +1181,22 @@ Proceed methodically:
|
|||||||
1. Read `AGENTS.md`.
|
1. Read `AGENTS.md`.
|
||||||
2. Read `CLAUDE.md`, if using Claude Code.
|
2. Read `CLAUDE.md`, if using Claude Code.
|
||||||
3. Read this `MIGRATION_BRIEF.md`.
|
3. Read this `MIGRATION_BRIEF.md`.
|
||||||
4. Inspect `./www.azinstitute4autism.com`.
|
4. Inspect the live public site.
|
||||||
5. Summarize a concise implementation plan.
|
5. Inspect the relevant live public site pages and identify discrepancies.
|
||||||
6. Scaffold Astro in `./www`.
|
6. Summarize a concise implementation plan.
|
||||||
7. Build extraction/reporting scripts.
|
7. Scaffold Astro in `./www`.
|
||||||
8. Extract pages, blog posts, assets, metadata, navigation, and forms.
|
8. Build extraction/reporting scripts.
|
||||||
9. Refactor repeated sections into Astro components.
|
9. Extract pages, blog posts, assets, metadata, navigation, and forms.
|
||||||
10. Configure content collections and Front Matter CMS.
|
10. Refactor repeated sections into Astro components.
|
||||||
11. Add multilingual structure.
|
11. Configure content collections and Front Matter CMS.
|
||||||
12. Add SEO, sitemap, robots, and redirects.
|
12. Add multilingual structure.
|
||||||
13. Add reports.
|
13. Add SEO, sitemap, robots, and redirects.
|
||||||
14. Run build and audits.
|
14. Add reports.
|
||||||
15. Fix errors.
|
15. Run build and audits.
|
||||||
16. Summarize what changed and what still needs manual review.
|
16. Fix errors.
|
||||||
|
17. Summarize what changed and what still needs manual review.
|
||||||
|
|
||||||
Prioritize a clean, maintainable foundation over a messy pixel-perfect clone.
|
Prioritize visual fidelity and content correctness while implementing them with
|
||||||
|
clean, maintainable Astro code. Do not trade away visible source design for a
|
||||||
|
generic foundation, and do not copy HubSpot-generated implementation merely to
|
||||||
|
obtain fidelity.
|
||||||
@@ -9,7 +9,12 @@ if [[ "${1:-}" == "exec" ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
exec codex \
|
exec codex \
|
||||||
--profile-v2 azi4a2-autonomous \
|
--profile azi4a2-autonomous \
|
||||||
--strict-config \
|
--strict-config \
|
||||||
|
--config 'approval_policy="never"' \
|
||||||
|
--config 'mcp_servers.playwright.default_tools_approval_mode="approve"' \
|
||||||
|
--config 'mcp_servers.playwright.tool_timeout_sec=300' \
|
||||||
|
--config 'mcp_servers.openaiDeveloperDocs.default_tools_approval_mode="approve"' \
|
||||||
|
--config 'mcp_servers.openaiDeveloperDocs.tool_timeout_sec=300' \
|
||||||
--cd "$repo_root" \
|
--cd "$repo_root" \
|
||||||
"$@"
|
"$@"
|
||||||
@@ -2,16 +2,18 @@
|
|||||||
"nodes": {
|
"nodes": {
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1728018373,
|
"lastModified": 1785090369,
|
||||||
"narHash": "sha256-NOiTvBbRLIOe5F6RbHaAh6++BNjsb149fGZd1T4+KBg=",
|
"narHash": "sha256-m0pDuRJG7EDo9ri+4Ksu83VsI+PlxNC9lNBfydejce4=",
|
||||||
"rev": "bc947f541ae55e999ffdb4013441347d83b00feb",
|
"owner": "NixOS",
|
||||||
"revCount": 688563,
|
"repo": "nixpkgs",
|
||||||
"type": "tarball",
|
"rev": "624af665418d3c65d544145b4d34ad696439570e",
|
||||||
"url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.1.688563%2Brev-bc947f541ae55e999ffdb4013441347d83b00feb/01925b66-3b70-71bd-b277-924a6c208ba7/source.tar.gz"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"type": "tarball",
|
"owner": "NixOS",
|
||||||
"url": "https://flakehub.com/f/NixOS/nixpkgs/0.1.%2A.tar.gz"
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
|
|||||||
@@ -1,25 +1,63 @@
|
|||||||
{
|
{
|
||||||
description = "A Nix-flake-based Node.js development environment";
|
description = "Arizona Institute for Autism Astro development environment";
|
||||||
|
|
||||||
inputs.nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/0.1.*.tar.gz";
|
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
|
|
||||||
outputs = { self, nixpkgs }:
|
outputs =
|
||||||
|
{ nixpkgs, ... }:
|
||||||
let
|
let
|
||||||
supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
|
supportedSystems = [
|
||||||
forEachSupportedSystem = f: nixpkgs.lib.genAttrs supportedSystems (system: f {
|
"x86_64-linux"
|
||||||
pkgs = import nixpkgs { inherit system; overlays = [ self.overlays.default ]; };
|
"aarch64-linux"
|
||||||
});
|
"aarch64-darwin"
|
||||||
|
];
|
||||||
|
|
||||||
|
forAllSystems =
|
||||||
|
function: nixpkgs.lib.genAttrs supportedSystems (system: function nixpkgs.legacyPackages.${system});
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
overlays.default = final: prev: rec {
|
devShells = forAllSystems (
|
||||||
nodejs = prev.nodejs;
|
pkgs:
|
||||||
yarn = (prev.yarn.override { inherit nodejs; });
|
let
|
||||||
};
|
developmentPackages = with pkgs; [
|
||||||
|
nodejs_24
|
||||||
|
git
|
||||||
|
curl
|
||||||
|
jq
|
||||||
|
ripgrep
|
||||||
|
fd
|
||||||
|
python3
|
||||||
|
];
|
||||||
|
in
|
||||||
|
{
|
||||||
|
default = pkgs.mkShell { packages = developmentPackages; };
|
||||||
|
}
|
||||||
|
// pkgs.lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux {
|
||||||
|
# Keep driver and browser revisions paired through the existing lockfile.
|
||||||
|
# Opt-in so ordinary content editing does not require browser downloads.
|
||||||
|
browser = pkgs.mkShell {
|
||||||
|
packages = developmentPackages ++ [ pkgs.playwright-driver ];
|
||||||
|
PLAYWRIGHT_MODULE = "${pkgs.playwright-driver}/index.mjs";
|
||||||
|
PLAYWRIGHT_BROWSERS_PATH = pkgs.playwright-driver.selectBrowsers {
|
||||||
|
withFirefox = true;
|
||||||
|
withWebkit = false;
|
||||||
|
withFfmpeg = false;
|
||||||
|
};
|
||||||
|
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = "1";
|
||||||
|
# Nix supplies browser libraries via patched runtime paths, not apt.
|
||||||
|
PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS = "true";
|
||||||
|
FONTCONFIG_FILE = pkgs.makeFontsConf {
|
||||||
|
fontDirectories = with pkgs; [
|
||||||
|
dejavu_fonts
|
||||||
|
liberation_ttf
|
||||||
|
noto-fonts
|
||||||
|
];
|
||||||
|
};
|
||||||
|
ASTRO_TELEMETRY_DISABLED = "1";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
devShells = forEachSupportedSystem ({ pkgs }: {
|
formatter = forAllSystems (pkgs: pkgs.nixfmt);
|
||||||
default = pkgs.mkShell {
|
|
||||||
packages = with pkgs; [ node2nix nodejs nodePackages.pnpm yarn ];
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
PUBLIC_AIA_API_BASE=https://api.azinstitute4autism.com
|
||||||
|
PUBLIC_COUNTERS_MODE=off
|
||||||
|
PUBLIC_COUNTERS_LIVE_APPROVED=false
|
||||||
|
PUBLIC_COUNTERS_MOCK_RESULT=success
|
||||||
|
# Optional override only: absent/empty means live on exactly 16 approved routes.
|
||||||
|
# PUBLIC_AIA_FORMS_MODE=off
|
||||||
|
# PUBLIC_AIA_FORMS_MODE=mock
|
||||||
|
PUBLIC_AIA_FORMS_MOCK_RESULT=success
|
||||||
|
# Explicit, local-only Form A meeting preview; requires PUBLIC_AIA_FORMS_MODE=mock.
|
||||||
|
PUBLIC_AIA_FORM_A_PREVIEW=false
|
||||||
|
PUBLIC_ALLOW_INDEXING=false
|
||||||
|
|
||||||
|
# Employee portal ciphertext is a non-PUBLIC build-only input.
|
||||||
|
# Never supply a passphrase through environment variables or build arguments.
|
||||||
|
AIA_PORTAL_LOCAL_PREVIEW_APPROVED=false
|
||||||
|
# AIA_PORTAL_CIPHERTEXT_FILE=/owner-controlled/private/ciphertext.json
|
||||||
|
# Explicit production-indexing input; it is never placed in Git or source snapshots.
|
||||||
|
AIA_PORTAL_PRODUCTION_APPROVED=false
|
||||||
|
# AIA_PORTAL_PRODUCTION_CIPHERTEXT_FILE=/absolute/private/employee-portal/approved-production-ciphertext.json
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
.astro/
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
reports/live-crawl.json
|
||||||
|
reports/*.png
|
||||||
@@ -0,0 +1,688 @@
|
|||||||
|
# Arizona Institute for Autism Astro Site
|
||||||
|
|
||||||
|
Static Astro + TypeScript migration of the public Arizona Institute for Autism
|
||||||
|
website.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
Run commands from this directory:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd www
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Astro prints the local development URL, normally:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
http://localhost:4321
|
||||||
|
```
|
||||||
|
|
||||||
|
The development server watches source files and refreshes the browser after
|
||||||
|
edits. Stop it with `Ctrl+C`.
|
||||||
|
|
||||||
|
## Environment Setup
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
|
||||||
|
- Node.js 22.12 or newer (see `package.json`); the Nix shell supplies Node.js 24
|
||||||
|
- npm
|
||||||
|
|
||||||
|
This repository also has a Nix development shell at the repository root:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd ..
|
||||||
|
nix --extra-experimental-features "nix-command flakes" develop
|
||||||
|
cd www
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Local Playwright environment (Linux)
|
||||||
|
|
||||||
|
From the repository root, enter the opt-in browser shell:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
nix --extra-experimental-features "nix-command flakes" develop .#browser
|
||||||
|
cd www
|
||||||
|
TMPDIR=/tmp npm run build
|
||||||
|
TMPDIR=/tmp node tools/playwright-smoke.mjs
|
||||||
|
```
|
||||||
|
|
||||||
|
The existing `flake.lock` pairs the Nix Playwright driver and Chromium/headless
|
||||||
|
shell. `PLAYWRIGHT_MODULE` points directly to that driver; `PLAYWRIGHT_BROWSERS_PATH`
|
||||||
|
selects its matching browsers. Fontconfig supplies Latin/Arabic fallback fonts.
|
||||||
|
The ordinary development shell and npm dependencies remain unchanged. This
|
||||||
|
browser shell is offered on Linux; the existing Darwin development shell is
|
||||||
|
unchanged. Do not run `npx playwright install` or mix an unrelated npm Playwright
|
||||||
|
version with these browser revisions.
|
||||||
|
|
||||||
|
The smoke test starts Astro preview and Chromium in the **same execution** on an
|
||||||
|
ephemeral `127.0.0.1` port. It blocks external and non-GET/HEAD requests, loads the
|
||||||
|
real Spanish privacy and Library pages, verifies staging robots/title/H1, captures
|
||||||
|
two full-page screenshots in a new `/tmp/aia-playwright-smoke-*` directory, and
|
||||||
|
closes browser/preview. It does not submit forms or exercise production APIs.
|
||||||
|
Success establishes browser transport only, not Phase 8 visual or human acceptance.
|
||||||
|
|
||||||
|
Use the [Phase 8 guarded browser procedure](reports/phase-8-host-browser-verification-2026-10-14.md)
|
||||||
|
for full review. Entering the Nix shell does not repair an already-running remote
|
||||||
|
MCP transport or relax sandbox permissions. If the autonomous environment cannot
|
||||||
|
access the Nix store, trusted certificates, sockets or browser runtime files, run
|
||||||
|
the same smoke test from the host terminal. Do not disable TLS verification or
|
||||||
|
bypass the sandbox. The Nix environment needs host validation before browser
|
||||||
|
availability can be claimed.
|
||||||
|
|
||||||
|
Copy the example environment file when testing the like/view counter or staging
|
||||||
|
SEO behavior:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
The default values are:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
PUBLIC_AIA_API_BASE=https://api.azinstitute4autism.com
|
||||||
|
PUBLIC_COUNTERS_MODE=off
|
||||||
|
PUBLIC_COUNTERS_LIVE_APPROVED=false
|
||||||
|
PUBLIC_ALLOW_INDEXING=false
|
||||||
|
```
|
||||||
|
|
||||||
|
The site still renders when the AIA API is unavailable. Migration and staging
|
||||||
|
builds emit `noindex,nofollow` by default. `PUBLIC_ALLOW_INDEXING=true` enables
|
||||||
|
production publication policy, including per-page noindex exceptions. Exercise
|
||||||
|
that mode locally with `PUBLIC_ALLOW_INDEXING=true npm run build`; this does
|
||||||
|
not deploy anything or change environment files. Run `npm run build` afterward
|
||||||
|
to restore staging output. Only explicitly authorized production deployments
|
||||||
|
should publish indexing-enabled artifacts.
|
||||||
|
|
||||||
|
Ordinary local builds keep counters off. `npm run deploy` deliberately and
|
||||||
|
non-overridably builds the production artifact with live counters, the approved
|
||||||
|
API origin and `PUBLIC_COUNTERS_LIVE_APPROVED=true`. Article pages post one
|
||||||
|
first-party view per real navigation; Library cards only batch-read statistics.
|
||||||
|
Likes use the existing POST/DELETE API and store only the visitor's chosen slug
|
||||||
|
set in localStorage. No view cookies or visitor identifiers are created. Run
|
||||||
|
`npm run test:counters`, then build in live mode and run `npm run
|
||||||
|
verify:counters -- --browser=chromium` (or `firefox`) for a fully intercepted
|
||||||
|
browser regression that cannot write production counters.
|
||||||
|
|
||||||
|
## View And Build
|
||||||
|
|
||||||
|
### Development Server
|
||||||
|
|
||||||
|
Use this while editing:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Useful development URLs include:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
/
|
||||||
|
/aba-therapy
|
||||||
|
/autism-evaluations
|
||||||
|
/learner-social-club
|
||||||
|
/client-consultation
|
||||||
|
/library
|
||||||
|
/es
|
||||||
|
/es/library
|
||||||
|
/ar/library
|
||||||
|
```
|
||||||
|
|
||||||
|
### Static Build
|
||||||
|
|
||||||
|
Generate the static site with staging indexing protection:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
The autonomous Codex sandbox denies `/etc/hosts`, so its Node runtime cannot
|
||||||
|
resolve `localhost`. Use this equivalent validation command only inside that
|
||||||
|
sandbox:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build:sandbox
|
||||||
|
```
|
||||||
|
|
||||||
|
Build output is written to:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
dist/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Preview Production Output
|
||||||
|
|
||||||
|
After a successful build:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run preview
|
||||||
|
```
|
||||||
|
|
||||||
|
Astro prints the preview URL, normally `http://localhost:4321`.
|
||||||
|
|
||||||
|
### Validate Before Committing
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
npm run audit:links
|
||||||
|
npm run audit:images
|
||||||
|
npm run audit:blog
|
||||||
|
```
|
||||||
|
|
||||||
|
The link audit reads `dist/` after a successful build. If rendered output is
|
||||||
|
unavailable, it audits generated source routes, relative and root-relative
|
||||||
|
Markdown links, and public assets instead. It writes:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
reports/broken-links.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Editing Content
|
||||||
|
|
||||||
|
Normal content editing happens in Markdown:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
src/content/pages/en
|
||||||
|
src/content/pages/es
|
||||||
|
src/content/pages/ar
|
||||||
|
src/content/blog/en
|
||||||
|
src/content/blog/es
|
||||||
|
src/content/blog/ar
|
||||||
|
src/content/authors/en
|
||||||
|
src/content/authors/es
|
||||||
|
src/content/authors/ar
|
||||||
|
```
|
||||||
|
|
||||||
|
Page and post frontmatter is validated by `src/content.config.ts`.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```md
|
||||||
|
---
|
||||||
|
title: "Example Article"
|
||||||
|
description: "Short search and social description."
|
||||||
|
slug: "example-article"
|
||||||
|
canonical: "https://www.azinstitute4autism.com/library/example-article"
|
||||||
|
lang: "en"
|
||||||
|
translationKey: "example-article"
|
||||||
|
featuredImage: "/assets/images/example.webp"
|
||||||
|
alt: "Descriptive image alt text"
|
||||||
|
date: "2026-06-07"
|
||||||
|
author: "rula-diab"
|
||||||
|
category: "Library"
|
||||||
|
tags:
|
||||||
|
- ABA Therapy
|
||||||
|
draft: false
|
||||||
|
---
|
||||||
|
|
||||||
|
Article content goes here.
|
||||||
|
```
|
||||||
|
|
||||||
|
Set `draft: true` while preparing unpublished content.
|
||||||
|
|
||||||
|
### Display Headings and Social Metadata
|
||||||
|
|
||||||
|
Pages and articles may supply an optional plain-text `displayH1` in frontmatter:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
title: "The existing SEO title"
|
||||||
|
displayH1: "The source-supported visible heading"
|
||||||
|
```
|
||||||
|
|
||||||
|
The override changes only the visible H1. It does not change the document or
|
||||||
|
social title, article cards, navigation labels, or related-post text. Empty,
|
||||||
|
whitespace-only and non-string overrides fail content validation. Without an
|
||||||
|
override, articles keep `title`, regular pages keep their existing service/page
|
||||||
|
heading mappings and title-derived fallback, and homepage/Library components
|
||||||
|
keep their existing headings. Homepage heading markup stays intact unless a
|
||||||
|
plain-text override is explicitly supplied. Library template SEO titles remain
|
||||||
|
unchanged. Front Matter CMS exposes the optional field for pages and blog posts.
|
||||||
|
|
||||||
|
Existing featured images and page banner fallbacks supply `og:image` and
|
||||||
|
`twitter:image`. Relative paths resolve against the public origin in
|
||||||
|
`src/data/site.json`, never a preview host or an external canonical. Existing
|
||||||
|
`alt` text supplies both image-alt metadata tags only when it describes the
|
||||||
|
selected featured image; no descriptions are invented for banner fallbacks.
|
||||||
|
Pages without a selected image omit image tags and retain the `summary` card;
|
||||||
|
image pages retain `summary_large_image`.
|
||||||
|
|
||||||
|
`og:locale` is omitted: the content languages `en`, `es` and `ar` do not establish
|
||||||
|
the regional `language_TERRITORY` values required by the
|
||||||
|
[Open Graph protocol](https://ogp.me/). Regional targeting remains a human
|
||||||
|
localization decision. HTML language/direction and hreflang are unchanged.
|
||||||
|
|
||||||
|
Run `npm run test:seo` for isolated real-Astro builds covering heading defaults,
|
||||||
|
overrides, schema rejection, image metadata and shared publication invariants
|
||||||
|
in staging and indexing modes. Fixtures never modify the working content or
|
||||||
|
the project's `dist/`. See the dated Phase 4A section of
|
||||||
|
[`reports/migration-summary.md`](reports/migration-summary.md) for source evidence.
|
||||||
|
|
||||||
|
### Publication, Canonicals, and Sitemap
|
||||||
|
|
||||||
|
`src/utils/publication-policy.ts` owns route eligibility, normalized public
|
||||||
|
URLs, canonical validation, robots values, and sitemap eligibility.
|
||||||
|
`src/utils/publication.ts` loads the Astro collections into that manifest.
|
||||||
|
`src/pages/[...slug].astro` generates every content route from eligible entries,
|
||||||
|
including home and Library indexes; their existing presentation components are
|
||||||
|
under `src/components/pages/`. Shared SEO and the generated sitemap use that
|
||||||
|
same manifest. The route and source-link audits also reuse the policy.
|
||||||
|
|
||||||
|
- `draft: true` means unpublished: no generated HTML route in either build mode.
|
||||||
|
The Arabic homepage `/ar` remains a draft; published Arabic Library routes
|
||||||
|
remain available. Navigation does not link to the unpublished homepage.
|
||||||
|
- `noindex: true` keeps a published route accessible, emits `noindex,follow`
|
||||||
|
in indexing-enabled builds, and excludes it from the sitemap. It defaults
|
||||||
|
to `false`. Staging always overrides this with `noindex,nofollow`.
|
||||||
|
- An omitted `canonical` gets the normalized public URL from `site.json` and
|
||||||
|
its local route. A supplied local canonical must match that route. Invalid
|
||||||
|
canonicals, duplicate canonical targets, and route collisions fail validation.
|
||||||
|
- Verified syndicated articles use their existing `canonical` field for the
|
||||||
|
external publisher URL. External-canonical routes are excluded from the
|
||||||
|
sitemap even when they are not marked noindex. Do not use canonical values
|
||||||
|
to create local routes or sitemap locations.
|
||||||
|
- `src/pages/sitemap.xml.ts` generates `dist/sitemap.xml` on every build.
|
||||||
|
Staging emits an empty sitemap and does not advertise it in `robots.txt`.
|
||||||
|
Indexing-enabled builds include only eligible, indexable, self-canonical
|
||||||
|
local routes. There is no hand-maintained `public/sitemap.xml`.
|
||||||
|
- `npm run generate:sitemap` is a compatibility command that runs the Astro
|
||||||
|
build; it no longer writes a separate public sitemap from frontmatter strings.
|
||||||
|
|
||||||
|
The verified noindex exceptions are `/schedule-consultation`, `/employee-portal`,
|
||||||
|
`/library/community-highlight-meet-rula-diab`, and
|
||||||
|
`/library/new-aia-scottsdale-office`. The last two also retain their verified
|
||||||
|
external canonicals. The dated evidence is in `reports/migration-summary.md`.
|
||||||
|
|
||||||
|
Run `npm run test:publication` for policy tests and isolated builds of the
|
||||||
|
actual application in staging and indexing-enabled modes. Fixtures test draft
|
||||||
|
content, draft home/Library indexes, missing and external canonicals, and
|
||||||
|
generated head/sitemap consistency without modifying the working site's output.
|
||||||
|
The policy is TypeScript; Node audit/test commands use `--experimental-strip-types`
|
||||||
|
for compatibility with the declared Node 22.12 minimum. Dependencies are unchanged.
|
||||||
|
|
||||||
|
After publication changes, build and run `npm run audit:routes -- --offline`
|
||||||
|
before `npm run audit:routes -- --check`. This creates a newly dated local
|
||||||
|
reconciliation using saved production observations, preserving earlier evidence.
|
||||||
|
Saved rendered metadata is specific to its build mode: after restoring the
|
||||||
|
normal staging build, reconcile that output again before checking it.
|
||||||
|
|
||||||
|
### Links And Buttons
|
||||||
|
|
||||||
|
Use normal Markdown links for links within prose:
|
||||||
|
|
||||||
|
```md
|
||||||
|
Read the [ABA therapy guide](/aba-therapy) for more information.
|
||||||
|
```
|
||||||
|
|
||||||
|
Prose links render as the live site's orange inline links. Do not place an
|
||||||
|
ordinary Markdown link on its own line merely to make it look like a button.
|
||||||
|
|
||||||
|
Use `.mdx` and the `Button` component only for a source-verified call to action:
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
import Button from '../../../components/Button.astro';
|
||||||
|
|
||||||
|
<Button href="/client-consultation">Request an Appointment</Button>
|
||||||
|
<Button href="/services" variant="outlined">Explore Services</Button>
|
||||||
|
<Button href="https://example.com" target="_blank">External Action</Button>
|
||||||
|
```
|
||||||
|
|
||||||
|
Available `Button` variants are `filled` (default), `outlined`, and `light`.
|
||||||
|
Available sizes are `default` and `small`. A `_blank` target automatically adds
|
||||||
|
`rel="noopener noreferrer"`.
|
||||||
|
|
||||||
|
Normal Markdown links remain preferred for ordinary page and article content.
|
||||||
|
|
||||||
|
### Stable Heading Links
|
||||||
|
|
||||||
|
Astro automatically generates an ID for each Markdown heading. In `.md` files,
|
||||||
|
when a section needs a stable, editorially controlled fragment link, add an
|
||||||
|
explicit ID with Sätteri heading-attribute syntax:
|
||||||
|
|
||||||
|
```md
|
||||||
|
## Accepted Insurance Carriers {#accepted-insurance-heading}
|
||||||
|
```
|
||||||
|
|
||||||
|
Link to it with the resulting fragment URL:
|
||||||
|
|
||||||
|
```md
|
||||||
|
[View accepted carriers](/insurance#accepted-insurance-heading)
|
||||||
|
```
|
||||||
|
|
||||||
|
Keep explicit IDs unique within the page, lowercase, and hyphenated. Treat a
|
||||||
|
published ID as permanent so inbound links do not break when heading text is
|
||||||
|
edited. The installed MDX parser rejects this shorthand, independently of
|
||||||
|
directive parsing. In `.mdx` files, use an explicit HTML heading instead:
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
<h2 id="accepted-insurance-heading">Accepted Insurance Carriers</h2>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Markdown Directives
|
||||||
|
|
||||||
|
Sätteri directive parsing is disabled. The content corpus has no intentional
|
||||||
|
container (`:::name`), leaf (`::name`), or text (`:name`) directives, and no
|
||||||
|
directive transformation plugins are registered. Enabling parsing silently
|
||||||
|
removed literal text such as `:1` in `1:1`, `:00` in times, and `:Plan` after
|
||||||
|
an adjacent colon. Heading attributes remain enabled.
|
||||||
|
|
||||||
|
Run `npm run test:markdown` after changing the Markdown/MDX configuration. It
|
||||||
|
builds temporary fixtures and the affected content through the actual Astro
|
||||||
|
configuration, checking ratios, punctuation, times, URLs, code, and explicit
|
||||||
|
heading IDs. It makes no production requests and leaves no fixture routes in
|
||||||
|
the site. Any future directive feature needs compatible rendering and must
|
||||||
|
preserve these literal-content checks.
|
||||||
|
|
||||||
|
### Front Matter CMS
|
||||||
|
|
||||||
|
The project includes `frontmatter.json` for the Front Matter CMS VS Code
|
||||||
|
extension. Open the `www` folder as the editor workspace so its content folders
|
||||||
|
and media paths resolve correctly.
|
||||||
|
|
||||||
|
### Edit The Homepage
|
||||||
|
|
||||||
|
The English and Spanish homepages are driven from the structured `home:`
|
||||||
|
frontmatter block in:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
src/content/pages/en/index.md
|
||||||
|
src/content/pages/es/index.md
|
||||||
|
```
|
||||||
|
|
||||||
|
`HomePage.astro` renders those named sections in order. Use the section names
|
||||||
|
as the editing surface:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
hero
|
||||||
|
servicesIntro
|
||||||
|
benefits
|
||||||
|
skills
|
||||||
|
insurance
|
||||||
|
esa
|
||||||
|
hsaFsa (optional; English only where published)
|
||||||
|
financialHelp
|
||||||
|
process
|
||||||
|
director
|
||||||
|
testimonials
|
||||||
|
logos (optional)
|
||||||
|
```
|
||||||
|
|
||||||
|
Keep homepage copy in those named blocks so the page remains easy to scan in
|
||||||
|
Front Matter CMS and VS Code. `src/content.config.ts` validates the structure.
|
||||||
|
`hsaFsa` uses `heading`, `body`, `image`, and `imageAlt`; omit the whole block
|
||||||
|
where no section is published. It renders between ESA and financial help.
|
||||||
|
`logos.items` holds `file`/`alt` pairs after testimonials. Process steps accept
|
||||||
|
an optional `href`; use an existing intake route and section ID in the same
|
||||||
|
language. Steps without a destination remain plain content.
|
||||||
|
|
||||||
|
### Edit The Team Page
|
||||||
|
|
||||||
|
The English Team page uses the validated `team:` frontmatter block in
|
||||||
|
`src/content/pages/en/team.md`. That block is the single source for the intro,
|
||||||
|
ordered clinical groups, ABA care team, member names/roles/images, and careers
|
||||||
|
content. The Markdown body contains no second roster. Update the structured
|
||||||
|
frontmatter rather than hardcoding people in `TeamPage.astro`; image filenames
|
||||||
|
refer to `public/assets/images`.
|
||||||
|
|
||||||
|
### Images And Downloads
|
||||||
|
|
||||||
|
Store content-referenced assets in:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
public/assets/images
|
||||||
|
public/assets/downloads
|
||||||
|
public/assets/media
|
||||||
|
```
|
||||||
|
|
||||||
|
Reference public assets from Markdown using root-relative paths:
|
||||||
|
|
||||||
|
```md
|
||||||
|

|
||||||
|
```
|
||||||
|
|
||||||
|
Use `src/assets/images` for images imported directly by Astro components and
|
||||||
|
processed by Astro.
|
||||||
|
|
||||||
|
### Components And Styling
|
||||||
|
|
||||||
|
Reusable page sections are in:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
src/components
|
||||||
|
src/layouts
|
||||||
|
```
|
||||||
|
|
||||||
|
Global styles and design tokens are in:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
src/styles/variables.css
|
||||||
|
src/styles/global.css
|
||||||
|
src/styles/rtl.css
|
||||||
|
```
|
||||||
|
|
||||||
|
Navigation, services, redirects, and site details are in:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
src/data
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adding Content
|
||||||
|
|
||||||
|
### Add An English Blog Post
|
||||||
|
|
||||||
|
1. Create `src/content/blog/en/post-slug.md`.
|
||||||
|
2. Add valid frontmatter and article Markdown.
|
||||||
|
3. Place referenced images in `public/assets/images`.
|
||||||
|
4. Run `npm run dev` and view `/library/post-slug`.
|
||||||
|
5. Run the production validation commands before committing.
|
||||||
|
|
||||||
|
### Add A Translation
|
||||||
|
|
||||||
|
Use the same nonempty `translationKey` across genuine translated entries in
|
||||||
|
the **same collection**. The key is an explicit equivalence declaration, not a
|
||||||
|
URL or an entry reference. Slugs may differ; similar slugs alone never create a
|
||||||
|
relationship. Do not change keys to manufacture translation pairs.
|
||||||
|
|
||||||
|
```txt
|
||||||
|
src/content/blog/en/post-slug.md
|
||||||
|
src/content/blog/es/post-slug.md
|
||||||
|
src/content/blog/ar/post-slug.md
|
||||||
|
```
|
||||||
|
|
||||||
|
The URLs become:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
/library/post-slug
|
||||||
|
/es/library/post-slug
|
||||||
|
/ar/library/post-slug
|
||||||
|
```
|
||||||
|
|
||||||
|
Arabic routes automatically render with `lang="ar"` and `dir="rtl"`.
|
||||||
|
|
||||||
|
The shared publication manifest supplies both the language switcher and SEO
|
||||||
|
alternates. A translation set needs at least two eligible members. Drafts,
|
||||||
|
unpublished placeholders, noindex pages, and external-canonical pages do not
|
||||||
|
participate. Mark placeholders `draft: true`; `/ar` remains unpublished.
|
||||||
|
Author records have no generated routes yet and cannot participate.
|
||||||
|
|
||||||
|
One member per language per collection/key is allowed, including draft members;
|
||||||
|
duplicates and blank/whitespace-padded keys fail validation. A lone key is valid
|
||||||
|
and emits no alternate: it does not assert that a particular missing language
|
||||||
|
exists. Dangling graph references, wrong-family targets, ineligible targets,
|
||||||
|
and nonreciprocal links fail validation. The rendered-output regression checks
|
||||||
|
also fail if an alternate target has no generated HTML file.
|
||||||
|
|
||||||
|
Indexing-enabled builds emit the same absolute hreflang links on every member,
|
||||||
|
including its own language. `x-default` points to the eligible English member
|
||||||
|
only; sets without one omit it. Staging suppresses hreflang while retaining the
|
||||||
|
eligible language menu and global `noindex,nofollow`. Unpaired/excluded pages
|
||||||
|
retain their current-language label without an empty interactive menu.
|
||||||
|
|
||||||
|
Run `npm run test:publication`, `npm run build`, and
|
||||||
|
`PUBLIC_ALLOW_INDEXING=true npm run build` to validate both modes. Finish with
|
||||||
|
`npm run build` to restore staging. See `reports/migration-summary.md` for
|
||||||
|
Phase 3B's exact route sets, evidence, and route-audit refresh commands.
|
||||||
|
|
||||||
|
Do not publish unreviewed machine translations. Do not invent translations;
|
||||||
|
substantive translation changes require human language review.
|
||||||
|
|
||||||
|
### Edit A Blog FAQ
|
||||||
|
|
||||||
|
Posts with live-style FAQ sections use `.mdx` and import:
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
import FAQAccordion from '../../../components/FAQAccordion.astro';
|
||||||
|
```
|
||||||
|
|
||||||
|
Edit the component's `label`, `heading`, and `items` data in the post. Keep
|
||||||
|
`answer` and `answerHtml` consistent. JSON-LD derives its answer text from the
|
||||||
|
actual visible `answerHtml` when supplied, otherwise from the rendered `answer`
|
||||||
|
fallback. Shared FAQ markup, styling, interaction, and schema generation remain
|
||||||
|
in `src/components/FAQAccordion.astro`, using `src/utils/structured-data.ts`.
|
||||||
|
|
||||||
|
The standalone FAQ page passes its rendered Markdown/MDX slot as `sourceHtml`
|
||||||
|
to the same component. Its server schema describes the direct H3 questions and
|
||||||
|
following answer nodes that the existing browser code turns into accordions.
|
||||||
|
Runtime-only sources without server content emit no invented FAQ schema. Do
|
||||||
|
not mix `items` with `sourceSelector`. Duplicate questions, multiple schema
|
||||||
|
producing accordions on one page, empty answers and conflicting canonical
|
||||||
|
props fail validation; consolidate questions into one accordion per page.
|
||||||
|
|
||||||
|
### Article and FAQ Structured Data
|
||||||
|
|
||||||
|
Article JSON-LD uses the existing SEO `title`, description, featured image,
|
||||||
|
language, `date`, optional `updatedDate`, and the exact locale/slug match in the
|
||||||
|
authors collection. Dates use calendar-day precision; article/card bylines
|
||||||
|
format that day in UTC to avoid shifting it on the build host. Missing
|
||||||
|
modification dates are omitted. Missing or ambiguous author references fail validation.
|
||||||
|
No author archive URL or publisher is invented. `displayH1` does not change
|
||||||
|
the schema headline. No new frontmatter fields are required.
|
||||||
|
|
||||||
|
BlogPosting and FAQPage emission reuse the manifest's `sitemapEligible` record
|
||||||
|
gate: drafts, noindex records and external-canonical records are excluded.
|
||||||
|
This gate is independent of the build's global indexing switch, so staging
|
||||||
|
can exercise schema while retaining its global noindex, empty sitemap and
|
||||||
|
absent hreflang. The existing site organization block remains unchanged.
|
||||||
|
|
||||||
|
These schemas describe the rendered local records; they do not certify that
|
||||||
|
older article dates or FAQ answers have been reconciled with production.
|
||||||
|
Phase 6C drift observations and validation results are recorded in
|
||||||
|
`reports/migration-summary.md`. Run `npm run test:seo` and
|
||||||
|
`npm run test:publication`, and validate both build modes before publication.
|
||||||
|
|
||||||
|
## Reports And Utilities
|
||||||
|
|
||||||
|
For the current migration checkpoint, next Codex phase, ratified decisions,
|
||||||
|
and outstanding cutover blockers, begin with the
|
||||||
|
**[living session handoff](reports/SESSION_HANDOFF.md)**. Update that short
|
||||||
|
handoff at each accepted milestone transition; detailed historical evidence
|
||||||
|
remains in `reports/migration-summary.md`.
|
||||||
|
|
||||||
|
Migration reports are stored in `reports/`.
|
||||||
|
|
||||||
|
Useful commands:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run crawl:live
|
||||||
|
npm run generate:sitemap
|
||||||
|
npm run generate:redirects
|
||||||
|
npm run audit:links
|
||||||
|
```
|
||||||
|
|
||||||
|
The live crawl is optional and may fail in restricted network environments.
|
||||||
|
|
||||||
|
## Referral CTA
|
||||||
|
|
||||||
|
The public `/referrals` page preserves the existing site's **Refer a Client**
|
||||||
|
button, linking directly to https://form.jotform.com/231638510219149. The
|
||||||
|
site does **not** embed a referral form or require Form C. This destination is
|
||||||
|
separate from Form A's 16 allowlisted placements. Legacy-hosted-form privacy,
|
||||||
|
recipient, accessibility and operational acceptance remain the owner's release
|
||||||
|
responsibility.
|
||||||
|
|
||||||
|
## Forms
|
||||||
|
|
||||||
|
As of the owner decision on 2026-10-30, `npm run dev` and `npm run build`
|
||||||
|
use live Jotform Form A on exactly 16 approved placements: 10 immediate frames
|
||||||
|
and six deferred bilingual service drawers. No Form A flags are required.
|
||||||
|
Public development-host forms are owner-authorized. This does not authorize cutover.
|
||||||
|
|
||||||
|
Use `PUBLIC_AIA_FORMS_MODE=off npm run build` for an offline artifact, or
|
||||||
|
`PUBLIC_AIA_FORMS_MODE=mock npm run dev` for synthetic fixtures. Missing/empty
|
||||||
|
mode means live; obsolete `isolated-test` and other invalid values produce an
|
||||||
|
explicit configuration error. Remove old approval/environment variables from
|
||||||
|
local settings. Indexing and form mode remain independent of counter mode.
|
||||||
|
|
||||||
|
See the [current Form A guide](integrations/jotform/README.md) for the exact
|
||||||
|
registry, safeguards, rollback and owner acceptance requirements. Form C remains
|
||||||
|
inactive. Employee-portal private input retains its own separate gates.
|
||||||
|
|
||||||
|
Verify matching output with
|
||||||
|
`node tools/phase10-artifact-verify.mjs --mode=staging --forms=live --counters=off`;
|
||||||
|
use `--mode=indexing` for indexing output and `--forms=off` or `--forms=mock`
|
||||||
|
for those explicit builds. Production counter artifacts require
|
||||||
|
`--counters=live`. No actual submissions or production counter writes run in
|
||||||
|
automated tests.
|
||||||
|
|
||||||
|
## Static releases and employee portal
|
||||||
|
|
||||||
|
The Contabo deployment interface is intentionally separate from NixOS changes.
|
||||||
|
Use [the release runbook](integrations/nixos/deploy.md) for `npm run deploy`,
|
||||||
|
dry-run, rollback, the encrypted production portal input and the separate
|
||||||
|
one-time employee-resource installer. The server-side installer is a root-only
|
||||||
|
POSIX shell script using NixOS base tools; it does not require Node.js on
|
||||||
|
Contabo. Ordinary releases never copy employee documents into `dist`;
|
||||||
|
protected staff and management resources remain under server-controlled Basic
|
||||||
|
Auth namespaces. After a successful unlock, the page keeps that role's code
|
||||||
|
only in memory and uses it for same-origin authenticated document fetches. The
|
||||||
|
response is downloaded as a local Blob, so employees do not receive a second
|
||||||
|
browser authentication prompt. Cross-role requests are stopped before
|
||||||
|
credentials are sent; direct resource URLs still require nginx authentication.
|
||||||
|
Codes are never put in URLs or browser storage. The accepted interim portal is
|
||||||
|
explicitly non-PHI.
|
||||||
|
|
||||||
|
### Google Ads consultation-form conversion handoff
|
||||||
|
|
||||||
|
The Google Ads conversion ID, label, and privacy-gated Astro integration
|
||||||
|
instructions are in
|
||||||
|
[`reports/google-ads-consultation-conversion.md`](reports/google-ads-consultation-conversion.md).
|
||||||
|
This is documentation only: **do not install Google tags or conversion snippets**
|
||||||
|
on health-service pages or turn on enhanced conversions until AIA's
|
||||||
|
privacy/HIPAA and advertising-policy review explicitly approves the complete
|
||||||
|
data flow. Any future measurement needs separately approved success and privacy semantics; the former API-success premise is historical and no tracking is active.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### `astro: command not found`
|
||||||
|
|
||||||
|
Dependencies are not installed:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### npm TLS or certificate errors
|
||||||
|
|
||||||
|
Do not disable npm certificate verification. Run `npm install` from a normal
|
||||||
|
host terminal or trusted development environment with working CA certificates.
|
||||||
|
|
||||||
|
### Link audit says build output is unavailable
|
||||||
|
|
||||||
|
Build first:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
npm run audit:links
|
||||||
|
npm run audit:images
|
||||||
|
npm run audit:blog
|
||||||
|
```
|
||||||
|
|
||||||
|
### A content page is missing
|
||||||
|
|
||||||
|
Check:
|
||||||
|
|
||||||
|
- its Markdown file is in the correct language/content folder
|
||||||
|
- `draft` is `false`
|
||||||
|
- `slug` is unique within that language and content type
|
||||||
|
- frontmatter satisfies `src/content.config.ts`
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { defineConfig } from 'astro/config';
|
||||||
|
import mdx from '@astrojs/mdx';
|
||||||
|
import { satteri } from '@astrojs/markdown-satteri';
|
||||||
|
import site from './src/data/site.json' with { type: 'json' };
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
site: site.url,
|
||||||
|
trailingSlash: 'never',
|
||||||
|
compressHTML: true,
|
||||||
|
integrations: [mdx()],
|
||||||
|
markdown: {
|
||||||
|
processor: satteri({
|
||||||
|
features: {
|
||||||
|
headingAttributes: true,
|
||||||
|
// No directive consumers: parsing them drops literal ratios and times.
|
||||||
|
directive: false
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
shikiConfig: { theme: 'github-light' }
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://frontmatter.codes/frontmatter.schema.json",
|
||||||
|
"frontMatter.framework.id": "astro",
|
||||||
|
"frontMatter.content.publicFolder": "public",
|
||||||
|
"frontMatter.content.pageFolders": [
|
||||||
|
{ "title": "Pages", "path": "[[workspace]]/www/src/content/pages" },
|
||||||
|
{ "title": "Blog", "path": "[[workspace]]/www/src/content/blog" },
|
||||||
|
{ "title": "Authors", "path": "[[workspace]]/www/src/content/authors" }
|
||||||
|
],
|
||||||
|
"frontMatter.taxonomy.contentTypes": [
|
||||||
|
{
|
||||||
|
"name": "page",
|
||||||
|
"pageBundle": false,
|
||||||
|
"fields": [
|
||||||
|
{ "title": "Title", "name": "title", "type": "string", "required": true },
|
||||||
|
{ "title": "Display H1", "name": "displayH1", "type": "string" },
|
||||||
|
{ "title": "Description", "name": "description", "type": "string" },
|
||||||
|
{ "title": "Slug", "name": "slug", "type": "string", "required": true },
|
||||||
|
{ "title": "Language", "name": "lang", "type": "choice", "choices": ["en", "ar", "es"] },
|
||||||
|
{ "title": "Canonical", "name": "canonical", "type": "string" },
|
||||||
|
{ "title": "Featured image", "name": "featuredImage", "type": "image" },
|
||||||
|
{ "title": "Alt text", "name": "alt", "type": "string" },
|
||||||
|
{ "title": "Translation key", "name": "translationKey", "type": "string" },
|
||||||
|
{ "title": "Draft", "name": "draft", "type": "boolean", "default": false }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "blog",
|
||||||
|
"pageBundle": false,
|
||||||
|
"fields": [
|
||||||
|
{ "title": "Title", "name": "title", "type": "string", "required": true },
|
||||||
|
{ "title": "Display H1", "name": "displayH1", "type": "string" },
|
||||||
|
{ "title": "Description", "name": "description", "type": "string" },
|
||||||
|
{ "title": "Slug", "name": "slug", "type": "string", "required": true },
|
||||||
|
{ "title": "Date", "name": "date", "type": "datetime", "required": true },
|
||||||
|
{ "title": "Author", "name": "author", "type": "string", "default": "rula-diab" },
|
||||||
|
{ "title": "Category", "name": "category", "type": "string" },
|
||||||
|
{ "title": "Tags", "name": "tags", "type": "tags" },
|
||||||
|
{ "title": "Language", "name": "lang", "type": "choice", "choices": ["en", "ar", "es"] },
|
||||||
|
{ "title": "Featured image", "name": "featuredImage", "type": "image" },
|
||||||
|
{ "title": "Alt text", "name": "alt", "type": "string" },
|
||||||
|
{ "title": "Canonical", "name": "canonical", "type": "string" },
|
||||||
|
{ "title": "Translation key", "name": "translationKey", "type": "string" },
|
||||||
|
{ "title": "Draft", "name": "draft", "type": "boolean", "default": false }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "author",
|
||||||
|
"pageBundle": false,
|
||||||
|
"fields": [
|
||||||
|
{ "title": "Name", "name": "name", "type": "string", "required": true },
|
||||||
|
{ "title": "Display name (published heading/byline)", "name": "displayName", "type": "string" },
|
||||||
|
{ "title": "Slug", "name": "slug", "type": "string", "required": true },
|
||||||
|
{ "title": "Description", "name": "description", "type": "string" },
|
||||||
|
{ "title": "Avatar", "name": "avatar", "type": "image" },
|
||||||
|
{ "title": "Language", "name": "lang", "type": "choice", "choices": ["en", "ar", "es"] },
|
||||||
|
{ "title": "Translation key", "name": "translationKey", "type": "string" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
# Employee portal compatibility and private migration
|
||||||
|
|
||||||
|
Recorded 2026-11-04. The clinical director accepted the collected, non-PHI
|
||||||
|
staff and management portal content and the interim shared-code presentation.
|
||||||
|
Browser-side decryption remains access friction rather than individual
|
||||||
|
authentication, revocation, a secure session, or a HIPAA determination.
|
||||||
|
|
||||||
|
The same successfully validated code authorizes protected document downloads
|
||||||
|
for that role during the current page session. The browser keeps it only in a
|
||||||
|
closure, performs a same-origin authenticated fetch, and saves the returned
|
||||||
|
document without opening nginx's native Basic Auth dialog. Staff uses the
|
||||||
|
`staff` username and stream `b`; management uses the `management` username and
|
||||||
|
stream `a`. Direct URLs remain nginx-protected, cross-role fetches are rejected
|
||||||
|
before credentials are sent, and failed authenticated downloads show an inline
|
||||||
|
error without direct-navigation fallback. No code enters a URL, browser
|
||||||
|
storage, public artifact, or log. Downloads use a bounded safe form of the
|
||||||
|
approved link label and preserve the validated extension from the protected
|
||||||
|
SHA-256 server destination; server resources are not renamed.
|
||||||
|
|
||||||
|
## Approved production ciphertext
|
||||||
|
|
||||||
|
An indexing-enabled production build may include one owner-approved encrypted
|
||||||
|
two-stream payload when both of these non-public build inputs are set:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
AIA_PORTAL_PRODUCTION_APPROVED=true
|
||||||
|
AIA_PORTAL_PRODUCTION_CIPHERTEXT_FILE=/absolute/private/employee-portal/approved-production-ciphertext.json
|
||||||
|
```
|
||||||
|
|
||||||
|
The build rejects missing, malformed, symlinked, oversized, wrong-mode, or
|
||||||
|
simultaneous local-preview/production inputs. It never decrypts the payload at
|
||||||
|
build time. `/employee-portal` remains noindex, sitemap-excluded and
|
||||||
|
hreflang-excluded. See [the deployment runbook](../nixos/deploy.md) for the
|
||||||
|
approved release and protected-resource installation commands.
|
||||||
|
|
||||||
|
## Source and private collection
|
||||||
|
|
||||||
|
The 42 approved non-PHI resources have already been collected under the ignored
|
||||||
|
`private/employee-portal/hubfs/` workspace. Collection and clinical review are
|
||||||
|
complete. Do not run a new collection pass for production preparation.
|
||||||
|
|
||||||
|
Production preparation is an owner-only, hidden-prompt sequence. The first
|
||||||
|
command identifies the encrypted streams from the staff and management codes;
|
||||||
|
the codes are never accepted in arguments, environment variables, files or
|
||||||
|
output:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run portal:prepare -- identify \
|
||||||
|
/home/alice/projects/wrk/aia/local-archives/aia-employee-portal-hubspot-footer-source.txt
|
||||||
|
npm run portal:prepare -- propose-mappings \
|
||||||
|
/home/alice/projects/wrk/aia/local-archives/aia-employee-portal-hubspot-footer-source.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Review the private mapping summary. The role rule maps staff-only references to
|
||||||
|
`/staff-resources/`, management-only references to `/management-resources/`, and
|
||||||
|
duplicates a shared source into both protected namespaces. After the owner
|
||||||
|
approves that operational policy, run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run portal:prepare -- approve-mappings \
|
||||||
|
/home/alice/projects/wrk/aia/local-archives/aia-employee-portal-hubspot-footer-source.txt
|
||||||
|
npm run portal:prepare -- rewrite-production \
|
||||||
|
/home/alice/projects/wrk/aia/local-archives/aia-employee-portal-hubspot-footer-source.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
The rewrite command prompts for both codes again, rewrites every approved legacy
|
||||||
|
resource URL, re-encrypts with fresh salts and IVs, verifies each role privately,
|
||||||
|
and atomically installs `approved-production-ciphertext.json` plus a matching
|
||||||
|
private `production-readiness.json`. It refuses unresolved `/hubfs/` references.
|
||||||
|
Prior manifests and production ciphertext are retained as timestamped private
|
||||||
|
backups.
|
||||||
|
|
||||||
|
### Mapping approval versus server verification
|
||||||
|
|
||||||
|
`mappingApproved`, `accessPolicyApproved`, and `rewriteApproved` mean the
|
||||||
|
verified collected source has an approved role and safe protected destination
|
||||||
|
and may be prepared or installed. They do not claim the destination exists.
|
||||||
|
`destinationInstalled`, `destinationHashVerified`,
|
||||||
|
`destinationPermissionsVerified`, and `destinationAccessVerified` remain false
|
||||||
|
until an authorized VPS rehearsal actually installs the file and verifies its
|
||||||
|
bytes, `root:nginx` permissions, Basic Auth boundary and protected URL. Local
|
||||||
|
preparation and installation accept the first state; release acceptance must
|
||||||
|
still complete the second state before browser cutover.
|
||||||
|
|
||||||
|
Read-only legacy footer source:
|
||||||
|
`/home/alice/projects/wrk/aia/local-archives/aia-employee-portal-hubspot-footer-source.txt`.
|
||||||
|
The original stays outside Git as rollback evidence. The 42-file collection and
|
||||||
|
its accepted content are complete; no current command downloads resources.
|
||||||
|
|
||||||
|
Private output is `private/employee-portal/`: directories 0700, manifests and
|
||||||
|
binary resources 0600. This exact subtree is gitignored and outside Astro's
|
||||||
|
public directory. Filenames are SHA-256 hashes of source URLs, independently
|
||||||
|
verified by content hashes. The manifest retains original references, labels,
|
||||||
|
view membership, MIME, bytes, status and delivery/classification decisions.
|
||||||
|
The manifest preserves the original source URL, collection hash, size, reference
|
||||||
|
membership and accepted source evidence. Public legacy reachability did not
|
||||||
|
infer public approval.
|
||||||
|
|
||||||
|
Before every artifact transfer, verify that only `dist` is copied and that it
|
||||||
|
contains no private manifest, document or real ciphertext unless that ciphertext
|
||||||
|
has separately been authorized for the local preview. Never archive the whole
|
||||||
|
working directory as a deployable artifact.
|
||||||
|
|
||||||
|
## Historical collection and rewriting recovery notes
|
||||||
|
|
||||||
|
The original source, collection manifests, collected bytes and prior ciphertext
|
||||||
|
revisions remain recovery evidence. Do not change their classifications or
|
||||||
|
destination flags by hand; use the production-preparation sequence above so
|
||||||
|
stream identity, references, source hashes and protected destinations remain
|
||||||
|
consistent. CSS/srcset dependencies remain an explicit error rather than a
|
||||||
|
silent rewrite.
|
||||||
|
|
||||||
|
## Local preview and content safety
|
||||||
|
|
||||||
|
Use a retained private ciphertext revision:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
AIA_PORTAL_LOCAL_PREVIEW_APPROVED=true \
|
||||||
|
AIA_PORTAL_CIPHERTEXT_FILE=/absolute/private/ciphertext-TIMESTAMP.json \
|
||||||
|
PUBLIC_AIA_FORMS_MODE=off PUBLIC_ALLOW_INDEXING=false \
|
||||||
|
npm run dev -- --host 127.0.0.1
|
||||||
|
```
|
||||||
|
|
||||||
|
The 2026-10-30 revision removes dependence on the retired Form A environment role. Form mode is independent; `off` above is optional for local testing and cannot grant portal approval. The page defaults to locked/unavailable without its own explicit approval and valid private input. Indexing
|
||||||
|
builds cannot include it. The codes never go to a server, storage or URL. No
|
||||||
|
native form submits; Enter and Unlock use local WebCrypto. Only the matching
|
||||||
|
stream is displayed; refresh or leaving the page locks it again.
|
||||||
|
|
||||||
|
The passive DOM renderer preserves headings, lists, text, tables and safe
|
||||||
|
HTTPS/mailto/tel links, with no referrer on links. It rejects scripts, inline
|
||||||
|
handlers, styles, media, embedded forms and unknown tags. Real portal markup
|
||||||
|
has not been decrypted or reviewed; any removed behavior must be reviewed with
|
||||||
|
the owner before claiming fidelity. It must not silently become arbitrary
|
||||||
|
script execution. No real encrypted stream or plaintext is in Git.
|
||||||
|
|
||||||
|
### Scoped presentation correction, 2026-10-30
|
||||||
|
|
||||||
|
The renderer now maps only `hs-button`, `content-button`, or
|
||||||
|
`portal-resource-button` anchor tokens, or exact inline AIA orange background
|
||||||
|
colors (`#ffbd59` / `#ff914d`), to the local `portal-resource-button` class.
|
||||||
|
It does not copy the original style or arbitrary classes. These are narrow
|
||||||
|
compatibility candidates, not a claim that encrypted real views have been
|
||||||
|
inspected. HTTPS/mailto/tel links retain the existing safe URL checks; contact
|
||||||
|
links receive a local dark-orange accent for readable contrast. Table scope
|
||||||
|
and bounded numeric row/column spans are preserved. All other executable or
|
||||||
|
unapproved attributes remain removed.
|
||||||
|
|
||||||
|
The follow-up styling correction recognizes the live portal's `Daily Lineup
|
||||||
|
Report` and `Monthly Lineup Report` link labels, even when their original
|
||||||
|
HubSpot classes or inline fills are absent. It maps only those links to the
|
||||||
|
existing orange resource-button style; arbitrary anchors remain ordinary links.
|
||||||
|
Both unlocked views now use the site's orange accent for list-item markers.
|
||||||
|
The source classes and styles still do not reach the passive DOM.
|
||||||
|
|
||||||
|
### Staff resources and Management Templates, 2026-11-01
|
||||||
|
|
||||||
|
The owner observed that the non-management (general-staff) portal still lost
|
||||||
|
HubSpot's resource-button presentation. After **sanitizing** the decrypted DOM,
|
||||||
|
a headed `Management Templates` section identifies the general-staff resource
|
||||||
|
view. Valid HTTP(S) resource links in that view receive only the local
|
||||||
|
`portal-resource-button` class; `mailto:` and `tel:` retain contact-link
|
||||||
|
styling. Neither destination validation nor removed legacy classes/styles,
|
||||||
|
scripts, media or HTML attributes are relaxed.
|
||||||
|
|
||||||
|
The `Management Templates` unordered list receives a local
|
||||||
|
`portal-template-grid` class: two columns at desktop widths (768px+),
|
||||||
|
one column on smaller screens. Semantic list order and keyboard tab order
|
||||||
|
are retained, with responsive full-width buttons. This is a presentation
|
||||||
|
change only: it does not infer or approve employee resource access.
|
||||||
|
Synthetic staff/management browser fixtures check the distinction and
|
||||||
|
responsive columns. The actual decrypted staff source remains private;
|
||||||
|
owner should confirm layout and links in the local preview before merging.
|
||||||
|
|
||||||
|
Scoped typography, spacing, lists, table rules and keyboard focus apply to both
|
||||||
|
unlocked streams. Synthetic staff/management fixtures exercise these mappings
|
||||||
|
at 320/390/768/1440px. Owner must inspect the real views privately to confirm
|
||||||
|
legacy button recognition and source fidelity; if a different class is used,
|
||||||
|
provide sanitized class semantics rather than decrypted content or credentials.
|
||||||
|
|
||||||
|
Before owner acceptance: validate two real view codes privately, inspect every
|
||||||
|
resource/category, responsive directories, focus, link semantics and sanitization
|
||||||
|
impact. Check wrong-code feedback, keyboard unlock, no-JS explanation and refresh.
|
||||||
|
Production must use a separately reviewed runtime ciphertext delivery mechanism;
|
||||||
|
this batch intentionally provides only a gated local build input. Browser-delivered
|
||||||
|
ciphertext is publicly downloadable by anyone able to reach that preview.
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
# AIA Form A Jotform styling and conditional-button guide
|
||||||
|
|
||||||
|
This directory contains the accepted CSS backup and integration guidance for AIA Form A. [`aia-form-a.css`](./aia-form-a.css) is intended for Jotform Classic Forms' **Form Designer → Styles → Inject Custom CSS** under owner-controlled configuration review. The existing repository CSS is the accepted backup; do not regenerate it from Astro styles.
|
||||||
|
|
||||||
|
The CSS translates the existing AIA form treatment: Rubik typography, blue labels and text, `#fcf9f5` inputs with `#edeef3` borders, the `#ffbd59` filled button, the `#ff914d` hover/focus border, four-pixel corners, visible validation errors, and strong keyboard focus. The Astro page supplies the heading, introduction, privacy-policy link, and surrounding card. The stylesheet therefore removes the Jotform surface rather than adding another header, footer, or nested card.
|
||||||
|
|
||||||
|
The selectors target Jotform Classic Forms identifiers such as `#form`, `.form-all`, `.form-line`, `.form-label`, `.form-sub-label`, `.form-required`, `.form-textbox`, `.form-dropdown`, `.form-textarea`, `.form-error-message`, and `.form-submit-button`. Jotform's final generated DOM must be checked in the account before relying on pixel-level parity. The stylesheet does not hide required indicators, error messages, honeypots, or focus outlines, and it does not request fonts or other external assets.
|
||||||
|
|
||||||
|
The current Builder theme reconciliation adds narrowly scoped `!important` declarations to the outer surface, labels, inputs, and submit-button treatments that Jotform's generated theme otherwise overrides. Error, required-indicator, and focus rules remain visible and are deliberately not reset. The account owner must still confirm the final generated DOM and rendered form after any Jotform theme or field change.
|
||||||
|
|
||||||
|
## Installation and comparison
|
||||||
|
|
||||||
|
1. Open the approved Form A in AIA's HIPAA-enabled Jotform account. Do not change its fields or settings from this repository workflow.
|
||||||
|
2. Preserve the approved account configuration; account changes and site cutover require their separate authorization.
|
||||||
|
3. Paste the contents of `aia-form-a.css` into Jotform's custom CSS area.
|
||||||
|
4. Compare the Jotform preview with the Astro Form A mock at 390px and 1440px. Check the form surface, label rhythm, input height, button fill, outlined hover/focus state, error state, and mobile reflow.
|
||||||
|
5. Check both English and Spanish configurations. Confirm translated labels, required indicators, validation messages, confirmation copy, and the accessible iframe title supplied by the parent Astro integration.
|
||||||
|
6. Test the form in a real embedded iframe and on the direct hosted-form fallback. Confirm the iframe has a practical mobile height, does not create horizontal overflow, and does not trap keyboard focus.
|
||||||
|
|
||||||
|
The optional Astro meeting preview requires `PUBLIC_AIA_FORMS_MODE=mock` and `PUBLIC_AIA_FORM_A_PREVIEW=true` in a non-indexing build. It does not load Jotform and is not a live submission test.
|
||||||
|
|
||||||
|
## Current Form A operation — superseding decision, 2026-10-30
|
||||||
|
|
||||||
|
The owner authorizes real Form A on the publicly reachable development host
|
||||||
|
`https://aia.web3app.dev/`. Normal development and static builds use **live** Form A
|
||||||
|
without environment flags. This intentionally supersedes the former isolated-test
|
||||||
|
and seven-route production approval gates. It is not site-cutover authorization.
|
||||||
|
|
||||||
|
From `www/`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run dev
|
||||||
|
npm run build
|
||||||
|
# Optional offline/restricted artifact:
|
||||||
|
PUBLIC_AIA_FORMS_MODE=off npm run build
|
||||||
|
# Optional synthetic fixtures:
|
||||||
|
PUBLIC_AIA_FORMS_MODE=mock npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
`PUBLIC_AIA_FORMS_MODE` is optional; missing/empty means `live`. The only accepted
|
||||||
|
values are `live`, `off`, and `mock`. Remove obsolete `isolated-test` settings from
|
||||||
|
local environment files; invalid explicit values stop the build with actionable
|
||||||
|
guidance. The former live/isolated/consultation/production approvals and form
|
||||||
|
environment role are removed, with no replacement gates. `PUBLIC_ALLOW_INDEXING`
|
||||||
|
controls publication independently: both staging and indexing builds use the same
|
||||||
|
16 placements. Explicit off/mock builds contain no real Form A iframe.
|
||||||
|
|
||||||
|
One typed registry in `src/utils/form-integration.ts` fixes every route, locale
|
||||||
|
and context. No visitor query, referrer, arbitrary URL or option preselection is
|
||||||
|
forwarded. The only origin/ID is `https://form.jotform.com/262946078314059`.
|
||||||
|
Every URL has exactly `formContext` and `language` parameters:
|
||||||
|
|
||||||
|
| Context | Routes | Loading |
|
||||||
|
|---|---|---|
|
||||||
|
| contact | `/contact` (en), `/es/contact` (es) | Immediate, 2 |
|
||||||
|
| landing | `/lp/aba-therapy`, `/lp/aba-insurance`, `/lp/aba-ahcccs`, `/lp/aba-private-pay`, `/lp/early-intervention` (en) | Immediate, 5 |
|
||||||
|
| consultation | `/client-consultation` (en), `/es/client-consultation` (es), `/schedule-consultation` (en) | Immediate, 3 |
|
||||||
|
| consultation | `/aba-therapy`, `/autism-evaluations`, `/learner-social-club` (en), and their `/es/` counterparts (es) | Deferred, 6 |
|
||||||
|
|
||||||
|
The context is presentation-only. Physical Form B is retired. No Form C is planned.
|
||||||
|
`/referrals` independently links to the existing hosted Jotform
|
||||||
|
`https://form.jotform.com/231638510219149`, not to Form A. That link is
|
||||||
|
owner-directed but its legacy form's runtime privacy/recipient acceptance remains
|
||||||
|
separate from Form A. Other routes cannot acquire Form A by setting a mode.
|
||||||
|
The typed future Academy service copy does not publish another page.
|
||||||
|
|
||||||
|
Live service drawers open at four seconds OR 50% scroll, canceling the unused
|
||||||
|
trigger. Dismissal or verified completion sets the existing shared 30-day
|
||||||
|
suppression timestamp. The existing service CTA overrides automatic suppression;
|
||||||
|
without JavaScript it remains a normal consultation-page link. Jotform loads only
|
||||||
|
when the drawer opens. Off mode never automatically opens an inactive drawer.
|
||||||
|
Mock mode retains the existing synthetic fixture workflow.
|
||||||
|
|
||||||
|
A verified `submission-completed` message must match the exact approved origin,
|
||||||
|
actual activated/open iframe window, approved form ID and action. It displays a
|
||||||
|
localized acknowledgment, waits five seconds, then uses the existing slide-out.
|
||||||
|
Duplicate messages cannot restart the countdown. Manual close/navigation cancels
|
||||||
|
it; suppression remains. Deliberate reopening after completion creates a fresh
|
||||||
|
iframe. Provider-envelope acceptance still requires the owner-controlled real
|
||||||
|
submission check; synthetic browser messages are not that acceptance.
|
||||||
|
|
||||||
|
The shared iframe preserves fixed responsive heights, accessible EN/ES titles,
|
||||||
|
`referrerpolicy="no-referrer"`, and
|
||||||
|
`sandbox="allow-forms allow-scripts allow-same-origin"`. No popup, top-navigation,
|
||||||
|
camera, microphone, payment or geolocation permissions are added. The hosted
|
||||||
|
fallback is outside the iframe and hidden without layout space until a 15-second
|
||||||
|
missing-load timeout. Late load hides it; disposal cancels timers. A localized
|
||||||
|
`noscript` fallback remains. Load does not prove application health or submission.
|
||||||
|
No helper script, cross-origin DOM inspection, arbitrary messaging or submission
|
||||||
|
forwarding is introduced. The official resize handler remains unimplemented and
|
||||||
|
conditionally acceptable only after its separate security/browser review.
|
||||||
|
|
||||||
|
Owner-reported direct EN/ES synthetic acceptance and sampled embedded acceptance
|
||||||
|
(English contact, Spanish contact and one ABA landing page) remain recorded as
|
||||||
|
owner evidence. This change does not independently verify Jotform or resolve its
|
||||||
|
Spanish consultation dropdown discrepancy, runtime telemetry, fluent-language,
|
||||||
|
screen-reader, final HTTPS-host or operational recipient review.
|
||||||
|
|
||||||
|
For form rollback, explicitly set `PUBLIC_AIA_FORMS_MODE=off`, rebuild, verify
|
||||||
|
`--forms=off`, and use the separately authorized artifact-switch procedure.
|
||||||
|
Removing that override restores live, so removing settings is **not** rollback.
|
||||||
|
Do not change the Jotform account as part of an Astro rollback.
|
||||||
|
|
||||||
|
Artifact checks (build matching output first):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
node tools/phase10-artifact-verify.mjs --mode=staging --forms=live
|
||||||
|
# For PUBLIC_ALLOW_INDEXING=true output:
|
||||||
|
node tools/phase10-artifact-verify.mjs --mode=indexing --forms=live
|
||||||
|
# For explicit off or mock output, select the matching --forms value.
|
||||||
|
```
|
||||||
|
|
||||||
|
Employee-portal ciphertext remains separately gated by its own approval, private
|
||||||
|
input and non-indexing preview requirement. Form A modes cannot authorize it.
|
||||||
|
|
||||||
|
## Existing professional referral destination (owner decision 2026-11-01)
|
||||||
|
|
||||||
|
`/referrals` has one `Refer a Client` link to
|
||||||
|
[the existing hosted Jotform](https://form.jotform.com/231638510219149). It does not embed Form A
|
||||||
|
or use Form C. No new form, question, Jotform integration, query parameter or
|
||||||
|
notification routing is introduced. Test the link and verify the separate
|
||||||
|
existing form's privacy, field necessity, recipients and confirmation with its
|
||||||
|
account owner before release.
|
||||||
|
|
||||||
|
## Form context and button labels
|
||||||
|
|
||||||
|
The current Form A design uses a hidden `formContext` value with fixed, approved values such as `contact` and `landing`. Jotform does not provide a native conditional rule that changes one Submit button's accessible label safely in every context. If owner-approved context-specific labels are required, use multiple native Jotform Submit buttons and Jotform's conditional visibility rules:
|
||||||
|
|
||||||
|
- Add one native Submit button for each approved context/language label.
|
||||||
|
- Show exactly one intended Submit button for the current, validated `formContext`.
|
||||||
|
- Keep every button a real Jotform Submit control; do not use CSS `::before`/`::after` text as a label.
|
||||||
|
- Verify that every visible variant submits through the same approved form and preserves the honeypot and validation behavior.
|
||||||
|
- Make the no-context and invalid-context states fail closed: no ambiguous extra button and no unintended submission path.
|
||||||
|
- Verify keyboard order, focus visibility, screen-reader names, English/Spanish labels, and mobile layout for every variant.
|
||||||
|
|
||||||
|
All five landing routes use Form A's fixed `landing` context. The nine consultation
|
||||||
|
placements use its fixed `consultation` context. Jotform owns native Submit labels
|
||||||
|
and conditions. Do not add an external Astro button that attempts to submit a
|
||||||
|
cross-origin iframe.
|
||||||
|
|
||||||
|
## Verification checklist
|
||||||
|
|
||||||
|
- [x] Direct English/Spanish Form A language handling, conditional email requirement, synthetic submission and native thank-you were owner-verified; this is not embedded-iframe acceptance.
|
||||||
|
- [x] Direct staff-notification/autoresponder delivery, generic no-answer/no-PDF content, fixed Reply-To, recipient access/retention, and absent webhooks/integrations were owner-verified for Form A.
|
||||||
|
- [ ] Exact remaining Form A consultation question properties, choices, conditional routing and availability, and consultation language/configuration, are approved.
|
||||||
|
- [ ] English and Spanish embedded-iframe labels, validation, confirmation, and button variants are reviewed by fluent-language owners.
|
||||||
|
- [ ] The hidden honeypot remains present, effective, and excluded from user-facing visual changes.
|
||||||
|
- [ ] Exactly one native Submit button is visible for every valid context.
|
||||||
|
- [ ] Invalid or missing context does not expose an unintended submission path.
|
||||||
|
- [ ] No PHI-bearing answer, PDF, recipient, redirect, webhook, or HTTP POST forwarding is inherited without privacy/security approval.
|
||||||
|
- [x] Owner-reported embedded synthetic submission and confirmation passed on English contact, Spanish contact and one ABA landing placement; record the exact ABA path in the owner test log before release.
|
||||||
|
- [ ] All sixteen placements have final responsive, keyboard, screen-reader and fluent-language acceptance, including timeout/fallback and error expansion.
|
||||||
|
- [ ] Jotform runtime requests, telemetry and cross-origin failure behavior are reviewed with privacy/security owners.
|
||||||
|
- [ ] No Jotform, Contabo, analytics, advertising, or production API request is made by off/mock builds.
|
||||||
|
|
||||||
|
Existing owner-verification scopes remain recorded in the contract/checklist. Outstanding consultation configuration acceptance, runtime/privacy, accessibility, fluent-language and site-release gates remain open. Default-live Form A is owner-authorized; this guide does not authorize Jotform account changes or site deployment.
|
||||||
@@ -0,0 +1,252 @@
|
|||||||
|
/* ==========================================================
|
||||||
|
AIA FORM A — JOTFORM CUSTOM CSS
|
||||||
|
Form ID: 262946078314059
|
||||||
|
Maximum form width: 640px
|
||||||
|
========================================================== */
|
||||||
|
|
||||||
|
/* Document and language selector */
|
||||||
|
|
||||||
|
html.supernova,
|
||||||
|
html.supernova body,
|
||||||
|
body.supernova {
|
||||||
|
background: #fff !important;
|
||||||
|
background-image: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Avoid horizontal overflow, including on the thank-you page */
|
||||||
|
body {
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Confirmation page styling */
|
||||||
|
body.thankyou,
|
||||||
|
body.thankyou #stage {
|
||||||
|
background: #fff !important;
|
||||||
|
background-image: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jfLangDd.jfLangDd--match-form {
|
||||||
|
padding: 12px 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form container */
|
||||||
|
|
||||||
|
form[id="262946078314059"] {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 640px !important;
|
||||||
|
margin: 8px auto !important;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-all {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
background: #fff !important;
|
||||||
|
border: 0 !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
color: #254080;
|
||||||
|
font-family: Rubik, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-all .form-section {
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Field layout */
|
||||||
|
|
||||||
|
.form-all .form-line {
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 0 21px !important;
|
||||||
|
background: transparent !important;
|
||||||
|
border: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-all .form-input-wide {
|
||||||
|
margin-top: 0 !important;
|
||||||
|
padding-top: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Labels and sublabels */
|
||||||
|
|
||||||
|
.form-all .form-label,
|
||||||
|
.form-all .form-label-left,
|
||||||
|
.form-all .form-label-right,
|
||||||
|
.form-all .form-label-top {
|
||||||
|
color: #254080 !important;
|
||||||
|
font-size: .875rem;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.35 !important;
|
||||||
|
margin: 0 0 6px !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-all .form-sub-label {
|
||||||
|
color: #254080;
|
||||||
|
font-size: .75rem;
|
||||||
|
line-height: 1.3 !important;
|
||||||
|
margin: 4px 0 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-all .form-sub-label-container {
|
||||||
|
margin-top: 0 !important;
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-all .form-required {
|
||||||
|
color: #a21c28 !important;
|
||||||
|
margin-left: .2rem;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inputs */
|
||||||
|
|
||||||
|
.form-all input.form-textbox,
|
||||||
|
.form-all select.form-dropdown,
|
||||||
|
.form-all textarea.form-textarea {
|
||||||
|
background: #fcf9f5 !important;
|
||||||
|
background-image: none !important;
|
||||||
|
border: 1px solid #edeef3 !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: #254080 !important;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
min-height: 2.75rem;
|
||||||
|
padding: .55rem .7rem;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Preserve two-column name fields */
|
||||||
|
|
||||||
|
.form-all .form-sub-label-container input.form-textbox {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Full-width phone and email fields */
|
||||||
|
|
||||||
|
.form-all
|
||||||
|
.form-line:is(
|
||||||
|
[data-type="control_phone"],
|
||||||
|
[data-type="control_email"]
|
||||||
|
)
|
||||||
|
:is(.form-input, .form-input-wide, input.form-textbox) {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Message field */
|
||||||
|
|
||||||
|
.form-all textarea.form-textarea {
|
||||||
|
width: 100% !important;
|
||||||
|
min-height: 5.5rem !important;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Radio buttons and checkboxes */
|
||||||
|
|
||||||
|
.form-all .form-radio-item,
|
||||||
|
.form-all .form-checkbox-item {
|
||||||
|
color: #254080;
|
||||||
|
line-height: 1.4;
|
||||||
|
margin: 0 .75rem .2rem 0 !important;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-all .form-radio,
|
||||||
|
.form-all .form-checkbox {
|
||||||
|
accent-color: #254080;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Submit button and wrapper */
|
||||||
|
|
||||||
|
.form-all .jf-form-buttons,
|
||||||
|
.form-all .form-buttons-wrapper {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
background: transparent !important;
|
||||||
|
border: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-all .form-submit-button,
|
||||||
|
.form-all button[type="submit"] {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-height: 3rem;
|
||||||
|
padding: .65rem 1.2rem;
|
||||||
|
background: #ffbd59 !important;
|
||||||
|
background-image: none !important;
|
||||||
|
border: 2px solid #ffbd59 !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
color: #254080 !important;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.4;
|
||||||
|
text-shadow: none !important;
|
||||||
|
transition: background .3s linear, border-color .3s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-all .form-submit-button:hover,
|
||||||
|
.form-all button[type="submit"]:hover {
|
||||||
|
background: transparent !important;
|
||||||
|
border-color: #ff914d !important;
|
||||||
|
color: #254080 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-all .form-submit-button:disabled,
|
||||||
|
.form-all button[type="submit"]:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: .65;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keyboard focus */
|
||||||
|
|
||||||
|
.form-all input.form-textbox:focus-visible,
|
||||||
|
.form-all select.form-dropdown:focus-visible,
|
||||||
|
.form-all textarea.form-textarea:focus-visible,
|
||||||
|
.form-all input[type="radio"]:focus-visible,
|
||||||
|
.form-all input[type="checkbox"]:focus-visible,
|
||||||
|
.form-all .form-submit-button:focus-visible {
|
||||||
|
outline: 3px solid #115bbd !important;
|
||||||
|
outline-offset: -3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Validation */
|
||||||
|
|
||||||
|
.form-all .form-line-error input.form-textbox,
|
||||||
|
.form-all .form-line-error select.form-dropdown,
|
||||||
|
.form-all .form-line-error textarea.form-textarea,
|
||||||
|
.form-all input.form-validation-error {
|
||||||
|
border-color: #a21c28 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-all .form-error-message,
|
||||||
|
.form-all .form-line-error .form-error-message {
|
||||||
|
color: #a21c28 !important;
|
||||||
|
font-size: .8rem;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.4;
|
||||||
|
margin: .3rem 0 0 !important;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Accessibility: reduced motion */
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.form-all .form-submit-button,
|
||||||
|
.form-all button[type="submit"] {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
# Inactive SOPS/age preparation, 2026-10-30
|
||||||
|
|
||||||
|
The owner selected SOPS, age and sops-nix. This directory is a template, not a
|
||||||
|
host configuration. The development flake remains unchanged. No real recipient,
|
||||||
|
key, encrypted secret or server user is supplied. No module is deployed.
|
||||||
|
|
||||||
|
Official references inspected for this pass:
|
||||||
|
- [SOPS documentation](https://getsops.io/docs/)
|
||||||
|
- [sops-nix installation and secret permissions](https://github.com/Mic92/sops-nix)
|
||||||
|
|
||||||
|
SOPS encrypts configuration at rest. sops-nix can supply decrypted values at
|
||||||
|
runtime under `/run/secrets` with explicit owners and modes. This does not make
|
||||||
|
browser ciphertext or publicly served documents private.
|
||||||
|
|
||||||
|
## Owner setup before activation
|
||||||
|
|
||||||
|
1. Supply the actual NixOS topology, host flake, deploy user, persisted key
|
||||||
|
location, backup owner, HTTPS termination and resource-service identity.
|
||||||
|
2. Pin a reviewed `github:Mic92/sops-nix` input in that host flake; import
|
||||||
|
`inputs.sops-nix.nixosModules.sops` and the reviewed template. Do not add the
|
||||||
|
input to the Astro development flake just to evaluate a deployment sketch.
|
||||||
|
3. The owner generates independent developer/recovery and VPS age identities in
|
||||||
|
private terminals. Record only public recipients in the reviewed SOPS
|
||||||
|
creation rule. Use distinct recipients, never a placeholder recipient. Keep
|
||||||
|
the VPS private identity on persistent root-controlled storage (0600), outside
|
||||||
|
`/nix/store`, Git, the public static root and CI artifacts.
|
||||||
|
4. Encrypt the approved credential material using SOPS with both recipients.
|
||||||
|
Review recipient access, least privilege and an offline recovery copy. SOPS
|
||||||
|
encrypted files may enter a deployment repository only after owner approval.
|
||||||
|
5. Set `aia.portalSecrets` options explicitly, then evaluate the complete host
|
||||||
|
configuration and test decryption on authorized nonproduction infrastructure.
|
||||||
|
Runtime consumers use the generated `.path`, not Nix evaluation of plaintext.
|
||||||
|
6. Decide which service reloads after rotation. Rotate application credentials
|
||||||
|
as well as SOPS recipients when revoking access: rewrapping alone does not
|
||||||
|
invalidate an already copied password. Test recovery and key restoration.
|
||||||
|
|
||||||
|
## Separate staff and management resource authorization
|
||||||
|
|
||||||
|
Keep original documents outside Astro `public`, `dist`, release archives and
|
||||||
|
CDN roots. Proposed delivery namespaces are `/staff-resources/` and
|
||||||
|
`/management-resources/`; neither is implemented or considered working here.
|
||||||
|
Use nginx `auth_request` to an approved authorization service or an equivalent
|
||||||
|
server-enforced policy with individually revocable identities and staff and
|
||||||
|
management roles. Staff credentials must not authorize management resources;
|
||||||
|
management access to staff resources must be an explicit policy. Backend file
|
||||||
|
locations must be internal, inaccessible through static aliases or alternate
|
||||||
|
paths. Never trust a browser-provided role header.
|
||||||
|
|
||||||
|
A separate shared Basic Auth credential file per namespace can be considered
|
||||||
|
only as an explicitly approved weaker interim model, not identity management or
|
||||||
|
automatic role inheritance. The template prepares separate secret inputs without
|
||||||
|
selecting that access model or installing an identity provider.
|
||||||
|
|
||||||
|
Use HTTPS, `Cache-Control: private, no-store`, no shared/CDN caching, and
|
||||||
|
`Referrer-Policy: no-referrer` for protected delivery. Avoid credentials and
|
||||||
|
sensitive resource paths in access/error logs; review monitoring and backups.
|
||||||
|
Authorization failures must fail closed, including at proxy/cache boundaries.
|
||||||
|
Test direct requests, alternate encodings, role crossover and rollback.
|
||||||
|
|
||||||
|
## Distinct boundaries and release checks
|
||||||
|
|
||||||
|
- SOPS: deployment secrets at rest and controlled runtime access.
|
||||||
|
- Portal HTML: shared-code browser decryption, offline-guessable access friction.
|
||||||
|
- Documents: require server authorization even if URLs are encrypted in the UI.
|
||||||
|
|
||||||
|
The approved production portal build uses a non-public encrypted two-stream
|
||||||
|
payload and retains the same browser-side decryption boundary. Approved non-PHI
|
||||||
|
documents install separately into the VPS-owned staff/management directories;
|
||||||
|
they are never copied into `dist`. Verify both protected namespaces, two-view
|
||||||
|
separation, artifact exclusions, code rotation, restore and rollback before
|
||||||
|
cutover. Revoking a static release cannot retract ciphertext or files already
|
||||||
|
downloaded by visitors. Site cutover remains separately authorized.
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
# Contabo release operations
|
||||||
|
|
||||||
|
This site publishes static content through the merged Contabo release manager.
|
||||||
|
It does not run `nixos-rebuild`, reload nginx, change DNS, or write directly to
|
||||||
|
`/srv/www/azinstitute4autism.com/www/dist`.
|
||||||
|
|
||||||
|
## Required local tools and private inputs
|
||||||
|
|
||||||
|
- Node.js 22+, Git, tar, SSH and rsync.
|
||||||
|
- Either an OpenSSH host alias already configured locally, or a normal SSH
|
||||||
|
identity accepted by the existing root receiver.
|
||||||
|
- For environment-only invocations: `AIA_DEPLOY_HOST=administrator@host`,
|
||||||
|
`AIA_DEPLOY_SSH_IDENTITY=/absolute/path/to/identity`, and
|
||||||
|
`AIA_PORTAL_PRODUCTION_CIPHERTEXT_FILE=/absolute/private/employee-portal/approved-production-ciphertext.json`.
|
||||||
|
- For positional invocations, use either `SSH_HOST CIPHERTEXT` (OpenSSH resolves
|
||||||
|
the host alias and identity) or `SSH_HOST SSH_IDENTITY CIPHERTEXT` (the CLI
|
||||||
|
supplies `-i` and `IdentitiesOnly=yes`).
|
||||||
|
|
||||||
|
The payload remains outside Git and the source snapshot. It is browser ciphertext
|
||||||
|
only; neither access code nor decrypted portal HTML enters an Astro environment
|
||||||
|
variable, public resource directory, build log, or deployment manifest.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
From `www/`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run deploy -- --dry-run
|
||||||
|
|
||||||
|
# Use a configured OpenSSH host alias and a private ciphertext path.
|
||||||
|
npm run deploy -- --dry-run aiar \
|
||||||
|
"$PWD/private/employee-portal/approved-production-ciphertext.json"
|
||||||
|
|
||||||
|
# Supply an explicit SSH identity; paths may contain spaces.
|
||||||
|
npm run deploy -- --dry-run 'root@66.94.121.180' \
|
||||||
|
"$HOME/.ssh/AIAMarketing" \
|
||||||
|
"$PWD/private/employee-portal/approved-production-ciphertext.json"
|
||||||
|
```
|
||||||
|
|
||||||
|
Dry-run performs the local production build, tests, artifact verification,
|
||||||
|
manifest creation and source-snapshot preparation. With no private production
|
||||||
|
payload it uses a generated synthetic two-stream fixture and explicitly reports
|
||||||
|
that the result is not production-ready. It never opens SSH or rsync.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
AIA_DEPLOY_HOST='root@approved-host' \
|
||||||
|
AIA_DEPLOY_SSH_IDENTITY="$HOME/.ssh/AIAMarketing" \
|
||||||
|
AIA_PORTAL_PRODUCTION_CIPHERTEXT_FILE="$PWD/private/employee-portal/approved-production-ciphertext.json" \
|
||||||
|
npm run deploy -- --confirm
|
||||||
|
```
|
||||||
|
|
||||||
|
The equivalent confirmed positional commands are:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run deploy -- --confirm aiar \
|
||||||
|
"$PWD/private/employee-portal/approved-production-ciphertext.json"
|
||||||
|
|
||||||
|
npm run deploy -- --confirm 'root@66.94.121.180' \
|
||||||
|
"$HOME/.ssh/AIAMarketing" \
|
||||||
|
"$PWD/private/employee-portal/approved-production-ciphertext.json"
|
||||||
|
```
|
||||||
|
|
||||||
|
When positional values are present, they take precedence over all three
|
||||||
|
deployment environment variables. Two positional values deliberately omit
|
||||||
|
`-i`, allowing the local OpenSSH configuration to select the identity. Dry-run
|
||||||
|
never opens SSH or rsync, even when host and identity arguments are provided.
|
||||||
|
Invalid modes, mixed confirmation flags, incomplete positional sets, unsafe
|
||||||
|
hosts, relative positional paths, and missing ciphertext files fail before a
|
||||||
|
release can be promoted.
|
||||||
|
|
||||||
|
The confirmed command builds with `PUBLIC_ALLOW_INDEXING=true`, Form A live and
|
||||||
|
the approved live counters configuration, then uploads only fresh commit-specific `staging/source-<sha>` and
|
||||||
|
`staging/dist-<sha>` directories. It runs, in order:
|
||||||
|
|
||||||
|
```text
|
||||||
|
aia-website-release verify
|
||||||
|
aia-website-release sync-source
|
||||||
|
aia-website-release promote
|
||||||
|
```
|
||||||
|
|
||||||
|
The VPS verifies the manifest, promotes an immutable release by atomic symlink,
|
||||||
|
and retains the prior release. A successful rsync transfer alone is not success.
|
||||||
|
|
||||||
|
Rollback requires the reviewed manifest retained at the server’s fixed path:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
AIA_DEPLOY_HOST='root@approved-host' \
|
||||||
|
AIA_DEPLOY_SSH_IDENTITY="$HOME/.ssh/AIAMarketing" \
|
||||||
|
npm run deploy:rollback -- FULL_APPROVED_SHA --confirm
|
||||||
|
```
|
||||||
|
|
||||||
|
## Employee resources: separate one-time operation
|
||||||
|
|
||||||
|
Approved non-PHI employee files are never part of an ordinary release. After
|
||||||
|
the VPS SOPS-backed Basic Auth material is independently ready:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run deploy:portal-resources -- --dry-run
|
||||||
|
|
||||||
|
AIA_DEPLOY_HOST='root@approved-host' \
|
||||||
|
AIA_DEPLOY_SSH_IDENTITY="$HOME/.ssh/AIAMarketing" \
|
||||||
|
npm run deploy:portal-resources -- --confirm
|
||||||
|
```
|
||||||
|
|
||||||
|
For the first install, the client extracts `portal-resource-install.sh` from the
|
||||||
|
current reviewed Git commit, uploads it into the root-controlled commit staging
|
||||||
|
directory, verifies its SHA-256 remotely, and invokes it through NixOS's base
|
||||||
|
`/bin/sh` and coreutils path. A prior public site release and a globally installed
|
||||||
|
Node.js runtime are not prerequisites. It reads only the private collection manifest,
|
||||||
|
accepts approved `STAFF-INTERNAL` and `MANAGEMENT-INTERNAL` mappings to
|
||||||
|
`/staff-resources/` and `/management-resources/`, verifies every local hash, rejects links/traversal and
|
||||||
|
uploads a root-controlled temporary tree. It never uses `--delete` for resource
|
||||||
|
directories and refuses to replace a different existing file. The VPS installs
|
||||||
|
regular files as `root:nginx` mode `0640` and reports metadata only.
|
||||||
|
|
||||||
|
Local mapping approval authorizes preparation and installation only. After the
|
||||||
|
authorized VPS rehearsal, verify installed hashes, ownership/mode, staff versus
|
||||||
|
management Basic Auth, and both protected URLs before deploying portal links.
|
||||||
|
|
||||||
|
Before public DNS cutover, test the site with approved host-header/SNI methods,
|
||||||
|
including the portal’s noindex response, valid ciphertext unlock, both protected
|
||||||
|
resource namespaces, denial cases, redirects, Form A, IPv4 and IPv6. Portal
|
||||||
|
shared-code rotation is coordinated: update VPS SOPS inputs and privately
|
||||||
|
re-encrypt the browser payload together. This interim portal is non-PHI and is
|
||||||
|
not an employee identity system.
|
||||||
|
|
||||||
|
The portal uses one code entry per page session. A successful decrypt retains
|
||||||
|
the code only in page memory and sends the matching fixed username (`staff` or
|
||||||
|
`management`) in an explicit same-origin Basic Authorization header when a
|
||||||
|
protected link is clicked. It downloads the authenticated response as a Blob
|
||||||
|
and never navigates directly to the protected URL after an error. Verify that
|
||||||
|
ordinary clicks download without a native credential dialog, inline errors
|
||||||
|
appear for denied or unavailable files, direct unauthenticated requests remain
|
||||||
|
401, and neither role can request the other namespace. Reloading, leaving the
|
||||||
|
page, or an Astro page swap discards the in-memory credential.
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# INACTIVE TEMPLATE. Not imported by the development flake or any deployed host.
|
||||||
|
# Requires an owner-reviewed sops-nix NixOS module import in the host flake.
|
||||||
|
{ config, lib, ... }:
|
||||||
|
let cfg = config.aia.portalSecrets;
|
||||||
|
in {
|
||||||
|
options.aia.portalSecrets = {
|
||||||
|
enable = lib.mkEnableOption "owner-reviewed portal deployment secrets";
|
||||||
|
encryptedFile = lib.mkOption { type = lib.types.path; description = "SOPS-encrypted YAML only; never a plaintext file"; };
|
||||||
|
ageKeyFile = lib.mkOption { type = lib.types.str; description = "Private persisted host age key path outside the Nix store"; };
|
||||||
|
readerUser = lib.mkOption { type = lib.types.str; description = "Approved resource authorization service user"; };
|
||||||
|
};
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
assertions = [{ assertion = lib.hasPrefix "/" cfg.ageKeyFile && !(lib.hasPrefix "/nix/store/" cfg.ageKeyFile);
|
||||||
|
message = "The host age identity must be an absolute runtime path outside the Nix store."; }];
|
||||||
|
sops.defaultSopsFile = cfg.encryptedFile;
|
||||||
|
sops.age.keyFile = cfg.ageKeyFile;
|
||||||
|
sops.age.generateKey = false;
|
||||||
|
sops.age.sshKeyPaths = [];
|
||||||
|
sops.gnupg.sshKeyPaths = [];
|
||||||
|
sops.secrets."portal/staff-credentials" = { owner = cfg.readerUser; mode = "0400"; };
|
||||||
|
sops.secrets."portal/management-credentials" = { owner = cfg.readerUser; mode = "0400"; };
|
||||||
|
# Consumers reference config.sops.secrets.<name>.path at runtime.
|
||||||
|
# Do not use builtins.readFile on a decrypted secret, environment.etc.text,
|
||||||
|
# PUBLIC_* values, or Astro/Vite imports to transport secrets.
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"name": "arizona-institute-for-autism",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22.12.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "astro dev",
|
||||||
|
"check": "astro check",
|
||||||
|
"build": "astro check && astro build",
|
||||||
|
"build:sandbox": "NODE_OPTIONS=--require=./tools/localhost-dns.cjs astro check && NODE_OPTIONS=--require=./tools/localhost-dns.cjs astro build",
|
||||||
|
"deploy": "node tools/deploy.mjs",
|
||||||
|
"deploy:rollback": "node tools/deploy-rollback.mjs",
|
||||||
|
"deploy:portal-resources": "node tools/deploy-portal-resources.mjs",
|
||||||
|
"portal:prepare": "node tools/employee-portal-migrate.mjs",
|
||||||
|
"preview": "astro preview",
|
||||||
|
"extract:blog-footers": "node tools/extract-blog-footers.mjs",
|
||||||
|
"crawl:live": "node tools/crawl-live-site.mjs",
|
||||||
|
"audit:links": "node --experimental-strip-types tools/audit-links.mjs",
|
||||||
|
"audit:images": "node tools/audit-page-imagery.mjs",
|
||||||
|
"audit:blog": "node tools/audit-blog-content.mjs",
|
||||||
|
"audit:routes": "node --experimental-strip-types tools/audit-routes.mjs",
|
||||||
|
"test:routes": "node --experimental-strip-types --test tools/audit-routes.test.mjs",
|
||||||
|
"test:markdown": "node --test tools/markdown.test.mjs",
|
||||||
|
"test:publication": "node --experimental-strip-types --test tools/publication.test.mjs",
|
||||||
|
"test:seo": "node --experimental-strip-types --test tools/seo.test.mjs",
|
||||||
|
"test:library": "node --experimental-strip-types --test tools/library.test.mjs",
|
||||||
|
"test:campaign": "node --test tools/campaign.test.mjs",
|
||||||
|
"test:counters": "node --experimental-strip-types --test tools/counters.test.mjs",
|
||||||
|
"verify:counters": "node tools/counters-browser.mjs",
|
||||||
|
"verify:campaign": "node tools/campaign-browser.mjs",
|
||||||
|
"generate:sitemap": "node tools/generate-sitemap.mjs",
|
||||||
|
"generate:redirects": "node tools/generate-redirects.mjs"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@astrojs/markdown-satteri": "^0.3.5",
|
||||||
|
"@astrojs/mdx": "^7.0.5",
|
||||||
|
"astro": "^7.1.5",
|
||||||
|
"cheerio": "^1.1.0",
|
||||||
|
"fast-glob": "^3.3.3",
|
||||||
|
"gray-matter": "^4.0.3",
|
||||||
|
"swiper": "^12.2.0",
|
||||||
|
"turndown": "^7.2.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@astrojs/check": "^0.9.10",
|
||||||
|
"typescript": "^6.0.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
Copyright 2017 The Playfair Display Project Authors (https://github.com/clauseggers/Playfair-Display), with Reserved Font Name "Playfair Display"
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
Copyright 2015 The Rubik Project Authors (https://github.com/googlefonts/rubik)
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
https://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 119 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 163 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 135 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 94 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg viewBox="113.85996 134.2596 679.2 315.6" xmlns="http://www.w3.org/2000/svg" id="Layer_1" style="max-height: 500px" width="679.2" height="315.6"><defs><style>.cls-1,.cls-2,.cls-3,.cls-4,.cls-5,.cls-6{stroke-width:0px;}.cls-2{fill:#27417f;}.cls-3{fill:#554f62;}.cls-4{fill:#254080;}.cls-5{fill:#aea990;}.cls-6{fill:#f2b85b;}</style></defs><path d="m176.47,162.76l22.18,57.09c.85,2.24,1.83,3.76,2.95,4.54,1.12.79,2.17,1.21,3.14,1.27v1.82c-1.46-.12-3.17-.2-5.14-.22-1.97-.03-3.95-.05-5.95-.05-2.54,0-4.91.01-7.09.05-2.18.03-3.94.11-5.28.22v-1.82c3.09-.12,5.05-.65,5.87-1.59.82-.94.62-2.95-.59-6.04l-16.36-44.17,1.46-1.64-15.09,39.45c-1.09,2.73-1.74,5-1.95,6.82-.22,1.82-.06,3.24.45,4.27.52,1.03,1.41,1.76,2.68,2.18,1.27.43,2.85.67,4.73.73v1.82c-1.82-.12-3.65-.2-5.5-.22-1.85-.03-3.59-.05-5.23-.05-1.46,0-2.74.01-3.86.05-1.12.03-2.14.11-3.05.22v-1.82c1.27-.3,2.55-1.06,3.82-2.27,1.27-1.21,2.48-3.27,3.63-6.18l21.09-54.45h3.09Zm9.54,38.54v1.82h-27.08l.91-1.82h26.18Z" class="cls-2"/><path d="m235.82,179.3c1.76,0,3.26.37,4.5,1.09,1.24.73,2.18,1.65,2.82,2.77.64,1.12.96,2.38.96,3.77,0,1.76-.53,3.26-1.59,4.5-1.06,1.24-2.47,1.86-4.23,1.86-1.51,0-2.8-.46-3.86-1.36-1.06-.91-1.59-2.15-1.59-3.73,0-1.33.35-2.47,1.04-3.41.7-.94,1.5-1.68,2.41-2.22-.6-.49-1.33-.7-2.18-.64-1.64.06-3.15.59-4.55,1.59-1.39,1-2.58,2.27-3.54,3.82-.97,1.54-1.73,3.19-2.27,4.95-.54,1.76-.82,3.4-.82,4.91v20.91c0,2.97.82,4.96,2.46,5.95,1.63,1,3.91,1.5,6.81,1.5v1.91c-1.39-.06-3.38-.14-5.95-.22-2.58-.09-5.28-.14-8.14-.14-2.18,0-4.36.05-6.54.14-2.18.09-3.85.16-5,.22v-1.91c2.37,0,4.04-.45,5.05-1.36,1-.91,1.5-2.64,1.5-5.18v-28.54c0-2.73-.46-4.74-1.36-6.04-.91-1.3-2.64-1.95-5.18-1.95v-1.91c1.94.18,3.82.27,5.64.27,2.06,0,3.99-.11,5.77-.32,1.78-.22,3.44-.53,4.95-.96v10.46c.73-1.76,1.7-3.45,2.91-5.09,1.21-1.63,2.66-2.98,4.36-4.04,1.7-1.06,3.58-1.59,5.64-1.59Z" class="cls-2"/><path d="m264,179.57v39.45c0,2.54.5,4.27,1.5,5.18,1,.91,2.68,1.36,5.05,1.36v1.91c-1.03-.06-2.62-.14-4.77-.22-2.16-.09-4.35-.14-6.59-.14s-4.38.05-6.59.14c-2.21.09-3.86.16-4.96.22v-1.91c2.37,0,4.04-.45,5.05-1.36,1-.91,1.5-2.64,1.5-5.18v-28.54c0-2.73-.46-4.74-1.36-6.04-.91-1.3-2.64-1.95-5.18-1.95v-1.91c1.94.18,3.82.27,5.64.27,2.06,0,3.99-.11,5.77-.32,1.78-.22,3.44-.53,4.95-.96Zm-5.45-21.91c1.88,0,3.45.59,4.73,1.78,1.27,1.18,1.91,2.65,1.91,4.41s-.64,3.23-1.91,4.41c-1.27,1.18-2.85,1.77-4.73,1.77s-3.45-.59-4.73-1.77c-1.27-1.18-1.91-2.65-1.91-4.41s.64-3.23,1.91-4.41c1.27-1.18,2.85-1.78,4.73-1.78Z" class="cls-2"/><path d="m312.54,180.12l-25.09,45.99h8.18c2.42,0,4.58-.49,6.45-1.46,1.88-.97,3.42-2.57,4.64-4.81,1.21-2.24,1.94-5.21,2.18-8.91h2.09c0,4.06.03,7.45.09,10.18s.15,4.97.27,6.73c-2.72-.12-5.54-.22-8.45-.27-2.91-.06-5.79-.09-8.63-.09-3.09,0-6.23.05-9.41.14-3.18.09-6.23.2-9.14.32l25.09-45.99h-8.36c-2.6,0-4.85.5-6.73,1.5-1.88,1-3.35,2.61-4.41,4.82-1.06,2.21-1.74,5.17-2.05,8.86h-2c0-4.12-.03-7.51-.09-10.18-.06-2.66-.15-4.91-.27-6.73,2.67.12,5.45.22,8.36.27,2.91.06,5.82.09,8.73.09s6.14-.05,9.32-.14c3.18-.09,6.26-.2,9.23-.32Z" class="cls-2"/><path d="m340.81,179.3c4.06,0,7.66.85,10.82,2.55,3.15,1.7,5.66,4.36,7.55,8,1.88,3.64,2.81,8.39,2.81,14.27s-.94,10.62-2.81,14.23c-1.88,3.61-4.4,6.24-7.55,7.91-3.15,1.67-6.76,2.5-10.82,2.5s-7.6-.83-10.82-2.5c-3.21-1.67-5.74-4.3-7.59-7.91-1.85-3.61-2.77-8.35-2.77-14.23s.92-10.63,2.77-14.27c1.85-3.64,4.38-6.3,7.59-8,3.21-1.7,6.82-2.55,10.82-2.55Zm0,1.82c-3.15,0-5.73,1.8-7.73,5.41-2,3.61-3,9.47-3,17.59s1,13.95,3,17.5c2,3.55,4.58,5.32,7.73,5.32s5.65-1.77,7.68-5.32c2.03-3.55,3.05-9.38,3.05-17.5s-1.02-13.98-3.05-17.59c-2.03-3.61-4.59-5.41-7.68-5.41Z" class="cls-2"/><path d="m400.07,179.3c2.42,0,4.41.32,5.95.96,1.55.64,2.83,1.5,3.86,2.59,1.09,1.15,1.86,2.64,2.32,4.46.45,1.82.68,4.27.68,7.36v24.36c0,2.54.5,4.27,1.5,5.18,1,.91,2.68,1.36,5.05,1.36v1.91c-1.03-.06-2.64-.14-4.82-.22-2.18-.09-4.33-.14-6.45-.14s-4.11.05-6.14.14c-2.03.09-3.56.16-4.59.22v-1.91c2.06,0,3.51-.45,4.37-1.36.85-.91,1.27-2.64,1.27-5.18v-26.9c0-1.76-.15-3.35-.45-4.77-.31-1.42-.94-2.58-1.91-3.45-.97-.88-2.43-1.32-4.37-1.32-2.24,0-4.23.64-5.95,1.91-1.73,1.27-3.09,3.02-4.09,5.23-1,2.21-1.5,4.68-1.5,7.41v21.91c0,2.54.44,4.27,1.32,5.18.88.91,2.32,1.36,4.32,1.36v1.91c-.97-.06-2.44-.14-4.41-.22-1.97-.09-4.01-.14-6.14-.14s-4.21.05-6.45.14c-2.24.09-3.91.16-5,.22v-1.91c2.37,0,4.04-.45,5.05-1.36,1-.91,1.5-2.64,1.5-5.18v-28.54c0-2.73-.46-4.74-1.36-6.04-.91-1.3-2.64-1.95-5.18-1.95v-1.91c1.94.18,3.82.27,5.64.27,2.06,0,3.99-.11,5.77-.32,1.78-.22,3.44-.53,4.95-.96v9.91c1.57-3.76,3.74-6.39,6.5-7.91,2.75-1.51,5.68-2.27,8.77-2.27Z" class="cls-2"/><path d="m436.06,228.11c-2.54,0-4.73-.49-6.54-1.46-1.82-.97-3.18-2.3-4.09-4-.91-1.69-1.36-3.63-1.36-5.82,0-2.67.6-4.83,1.82-6.5,1.21-1.67,2.77-3.02,4.69-4.04,1.91-1.03,3.94-1.89,6.09-2.59,2.15-.7,4.18-1.36,6.09-2,1.91-.64,3.48-1.38,4.73-2.22,1.24-.85,1.86-1.97,1.86-3.36v-6.18c0-2.18-.35-3.93-1.05-5.23-.7-1.3-1.63-2.22-2.81-2.77-1.19-.55-2.59-.82-4.23-.82-1.4,0-2.88.2-4.46.59-1.57.39-2.81,1.17-3.72,2.32,1.33.37,2.44,1.08,3.32,2.14.88,1.06,1.32,2.38,1.32,3.95,0,1.7-.56,3.03-1.68,4s-2.53,1.45-4.Line truncated
|
||||||
|
After Width: | Height: | Size: 33 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 160" fill="none" stroke="#E5AE56" stroke-width="2">
|
||||||
|
<path d="m67 13 2-10h22l2 10 13 5 9-6 16 16-6 9 5 13 10 2v22l-10 2-5 13 6 9-16 16-9-6-13 5-2 10H69l-2-10-13-5-9 6-16-16 6-9-5-13-10-2V52l10-2 5-13-6-9 16-16 9 6Z" transform="translate(0 17)"/>
|
||||||
|
<circle cx="80" cy="80" r="26"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 346 B |
|
After Width: | Height: | Size: 109 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 109 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 38 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg viewBox="113.85996 134.2596 679.2 315.6" xmlns="http://www.w3.org/2000/svg" id="Layer_1" style="max-height: 500px" width="679.2" height="315.6"><defs><style>.cls-1,.cls-2,.cls-3,.cls-4,.cls-5,.cls-6{stroke-width:0px;}.cls-2{fill:#27417f;}.cls-3{fill:#554f62;}.cls-4{fill:#254080;}.cls-5{fill:#aea990;}.cls-6{fill:#f2b85b;}</style></defs><path d="m176.47,162.76l22.18,57.09c.85,2.24,1.83,3.76,2.95,4.54,1.12.79,2.17,1.21,3.14,1.27v1.82c-1.46-.12-3.17-.2-5.14-.22-1.97-.03-3.95-.05-5.95-.05-2.54,0-4.91.01-7.09.05-2.18.03-3.94.11-5.28.22v-1.82c3.09-.12,5.05-.65,5.87-1.59.82-.94.62-2.95-.59-6.04l-16.36-44.17,1.46-1.64-15.09,39.45c-1.09,2.73-1.74,5-1.95,6.82-.22,1.82-.06,3.24.45,4.27.52,1.03,1.41,1.76,2.68,2.18,1.27.43,2.85.67,4.73.73v1.82c-1.82-.12-3.65-.2-5.5-.22-1.85-.03-3.59-.05-5.23-.05-1.46,0-2.74.01-3.86.05-1.12.03-2.14.11-3.05.22v-1.82c1.27-.3,2.55-1.06,3.82-2.27,1.27-1.21,2.48-3.27,3.63-6.18l21.09-54.45h3.09Zm9.54,38.54v1.82h-27.08l.91-1.82h26.18Z" class="cls-2"/><path d="m235.82,179.3c1.76,0,3.26.37,4.5,1.09,1.24.73,2.18,1.65,2.82,2.77.64,1.12.96,2.38.96,3.77,0,1.76-.53,3.26-1.59,4.5-1.06,1.24-2.47,1.86-4.23,1.86-1.51,0-2.8-.46-3.86-1.36-1.06-.91-1.59-2.15-1.59-3.73,0-1.33.35-2.47,1.04-3.41.7-.94,1.5-1.68,2.41-2.22-.6-.49-1.33-.7-2.18-.64-1.64.06-3.15.59-4.55,1.59-1.39,1-2.58,2.27-3.54,3.82-.97,1.54-1.73,3.19-2.27,4.95-.54,1.76-.82,3.4-.82,4.91v20.91c0,2.97.82,4.96,2.46,5.95,1.63,1,3.91,1.5,6.81,1.5v1.91c-1.39-.06-3.38-.14-5.95-.22-2.58-.09-5.28-.14-8.14-.14-2.18,0-4.36.05-6.54.14-2.18.09-3.85.16-5,.22v-1.91c2.37,0,4.04-.45,5.05-1.36,1-.91,1.5-2.64,1.5-5.18v-28.54c0-2.73-.46-4.74-1.36-6.04-.91-1.3-2.64-1.95-5.18-1.95v-1.91c1.94.18,3.82.27,5.64.27,2.06,0,3.99-.11,5.77-.32,1.78-.22,3.44-.53,4.95-.96v10.46c.73-1.76,1.7-3.45,2.91-5.09,1.21-1.63,2.66-2.98,4.36-4.04,1.7-1.06,3.58-1.59,5.64-1.59Z" class="cls-2"/><path d="m264,179.57v39.45c0,2.54.5,4.27,1.5,5.18,1,.91,2.68,1.36,5.05,1.36v1.91c-1.03-.06-2.62-.14-4.77-.22-2.16-.09-4.35-.14-6.59-.14s-4.38.05-6.59.14c-2.21.09-3.86.16-4.96.22v-1.91c2.37,0,4.04-.45,5.05-1.36,1-.91,1.5-2.64,1.5-5.18v-28.54c0-2.73-.46-4.74-1.36-6.04-.91-1.3-2.64-1.95-5.18-1.95v-1.91c1.94.18,3.82.27,5.64.27,2.06,0,3.99-.11,5.77-.32,1.78-.22,3.44-.53,4.95-.96Zm-5.45-21.91c1.88,0,3.45.59,4.73,1.78,1.27,1.18,1.91,2.65,1.91,4.41s-.64,3.23-1.91,4.41c-1.27,1.18-2.85,1.77-4.73,1.77s-3.45-.59-4.73-1.77c-1.27-1.18-1.91-2.65-1.91-4.41s.64-3.23,1.91-4.41c1.27-1.18,2.85-1.78,4.73-1.78Z" class="cls-2"/><path d="m312.54,180.12l-25.09,45.99h8.18c2.42,0,4.58-.49,6.45-1.46,1.88-.97,3.42-2.57,4.64-4.81,1.21-2.24,1.94-5.21,2.18-8.91h2.09c0,4.06.03,7.45.09,10.18s.15,4.97.27,6.73c-2.72-.12-5.54-.22-8.45-.27-2.91-.06-5.79-.09-8.63-.09-3.09,0-6.23.05-9.41.14-3.18.09-6.23.2-9.14.32l25.09-45.99h-8.36c-2.6,0-4.85.5-6.73,1.5-1.88,1-3.35,2.61-4.41,4.82-1.06,2.21-1.74,5.17-2.05,8.86h-2c0-4.12-.03-7.51-.09-10.18-.06-2.66-.15-4.91-.27-6.73,2.67.12,5.45.22,8.36.27,2.91.06,5.82.09,8.73.09s6.14-.05,9.32-.14c3.18-.09,6.26-.2,9.23-.32Z" class="cls-2"/><path d="m340.81,179.3c4.06,0,7.66.85,10.82,2.55,3.15,1.7,5.66,4.36,7.55,8,1.88,3.64,2.81,8.39,2.81,14.27s-.94,10.62-2.81,14.23c-1.88,3.61-4.4,6.24-7.55,7.91-3.15,1.67-6.76,2.5-10.82,2.5s-7.6-.83-10.82-2.5c-3.21-1.67-5.74-4.3-7.59-7.91-1.85-3.61-2.77-8.35-2.77-14.23s.92-10.63,2.77-14.27c1.85-3.64,4.38-6.3,7.59-8,3.21-1.7,6.82-2.55,10.82-2.55Zm0,1.82c-3.15,0-5.73,1.8-7.73,5.41-2,3.61-3,9.47-3,17.59s1,13.95,3,17.5c2,3.55,4.58,5.32,7.73,5.32s5.65-1.77,7.68-5.32c2.03-3.55,3.05-9.38,3.05-17.5s-1.02-13.98-3.05-17.59c-2.03-3.61-4.59-5.41-7.68-5.41Z" class="cls-2"/><path d="m400.07,179.3c2.42,0,4.41.32,5.95.96,1.55.64,2.83,1.5,3.86,2.59,1.09,1.15,1.86,2.64,2.32,4.46.45,1.82.68,4.27.68,7.36v24.36c0,2.54.5,4.27,1.5,5.18,1,.91,2.68,1.36,5.05,1.36v1.91c-1.03-.06-2.64-.14-4.82-.22-2.18-.09-4.33-.14-6.45-.14s-4.11.05-6.14.14c-2.03.09-3.56.16-4.59.22v-1.91c2.06,0,3.51-.45,4.37-1.36.85-.91,1.27-2.64,1.27-5.18v-26.9c0-1.76-.15-3.35-.45-4.77-.31-1.42-.94-2.58-1.91-3.45-.97-.88-2.43-1.32-4.37-1.32-2.24,0-4.23.64-5.95,1.91-1.73,1.27-3.09,3.02-4.09,5.23-1,2.21-1.5,4.68-1.5,7.41v21.91c0,2.54.44,4.27,1.32,5.18.88.91,2.32,1.36,4.32,1.36v1.91c-.97-.06-2.44-.14-4.41-.22-1.97-.09-4.01-.14-6.14-.14s-4.21.05-6.45.14c-2.24.09-3.91.16-5,.22v-1.91c2.37,0,4.04-.45,5.05-1.36,1-.91,1.5-2.64,1.5-5.18v-28.54c0-2.73-.46-4.74-1.36-6.04-.91-1.3-2.64-1.95-5.18-1.95v-1.91c1.94.18,3.82.27,5.64.27,2.06,0,3.99-.11,5.77-.32,1.78-.22,3.44-.53,4.95-.96v9.91c1.57-3.76,3.74-6.39,6.5-7.91,2.75-1.51,5.68-2.27,8.77-2.27Z" class="cls-2"/><path d="m436.06,228.11c-2.54,0-4.73-.49-6.54-1.46-1.82-.97-3.18-2.3-4.09-4-.91-1.69-1.36-3.63-1.36-5.82,0-2.67.6-4.83,1.82-6.5,1.21-1.67,2.77-3.02,4.69-4.04,1.91-1.03,3.94-1.89,6.09-2.59,2.15-.7,4.18-1.36,6.09-2,1.91-.64,3.48-1.38,4.73-2.22,1.24-.85,1.86-1.97,1.86-3.36v-6.18c0-2.18-.35-3.93-1.05-5.23-.7-1.3-1.63-2.22-2.81-2.77-1.19-.55-2.59-.82-4.23-.82-1.4,0-2.88.2-4.46.59-1.57.39-2.81,1.17-3.72,2.32,1.33.37,2.44,1.08,3.32,2.14.88,1.06,1.32,2.38,1.32,3.95,0,1.7-.56,3.03-1.68,4s-2.53,1.45-4.Line truncated
|
||||||
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 42 KiB |
@@ -0,0 +1,24 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="450" viewBox="0 0 800 450">
|
||||||
|
<title>نموذج المقدمة والسلوك والنتيجة اللاحقة</title>
|
||||||
|
<image href="data:image/webp;base64,UklGRqqoAABXRUJQVlA4IJ6oAACQlgKdASogA8IBPpE+mkoloyIhp7FMkLASCWNu0atuo+8/hc72vn/l2SXST+W89GyhV23khTWeu5Lez2L9Q/u9qvK/yf+36EPIvir8//Fet3/n8DPgP/B5wD5X/g9aH9Z/3nsM/1Do4+aDzdPT1/dvUe/yv+49cz1oPRC6aH+3ecpnGX+1/1/ox+efxn+78Qfzf7Z/ef379zfiF/e8zfwf+r5qfcx+V/kfa1/gf+r/OeUv67/R/9f1DvzL+o/671YOSviA8P+9P+r9g722+2/9n/MflT8TH6H/t9Kf4b/X/+X3Af1x/4fsj/4/Hp/J/9f2CP6F/i//P93X1D/7P/2/4/pd+wf/n7h/9B/wX/l/zXt5///3a/vR///dw/dT//oDY+ZlmGz0aurLMNno0wobo7evPt0FDdHbsSKEbpWYbPRpfGdegq7sgIyQddLHyeE1E+dff2k5GMjfvtugobortGfJ+m0JAPReL3Cux8zLMLsk66IAKK3r6kForL4yMq7h8RGPM2N6HxQd+3O676CToBfiaMPUQXSd9fYRTv8nj/+pLeptrihK71mSB/pvDFxxkwEZdYletgvowIer5iRsG3C5HGHv9kbQAvWJqr0ocqUft/OfBN1Ap91BX9KoXPfB3/8BU5k2BXVrQCtNKGAU1FkQZYXiwSnxCAtWk7mBm4QqD4hww0rYerXIjRw/BLsLDZ9KmRB6GN9SHTUWWwGP23X541M360l1EiWdWFrUonM8FeUUHTm0QGGqDYoWh9fPX8Oh5KD4uEEthsveAgqwXPG7A60+Xzt2JXPcojUWGyIHoT5u+X/t2rwO0oH3fBIro1nWuwwCOwdRK9Cg2c2qeBCbrKOSHvexq5fz/wLUCL/PQTiLq34v9qDAeAxKFmpny7QDHycQo4zz89R/hss3SR7/+KM/17eCKnRVzsIJsezet7HUXglrcbVrUohbYEDQv0uPRly6m6W2YQ/HblOm6Xc9vIMKGsXSJgQOv/iiYmq7hetYde5gF2ZiF6fd50xrebMJstfkB664BPxw7ehmYO12uRS35iX5npdM5i7Ga9h3LkITUuKIxOJHpdZMqthzz5YCllzGGl1Z1kDMN08j8/9wdMuoHbnjwnfcqovTEi75p+kUKhPYxdwcLXLZOIag6UWTUVcoGAZbIeZlzUgKxSxPa9AaSDZO8MhqYTQSVI8B7EJ6j3M2NXyRX3j8n5Qn9LBB0xwQHfXnwTh6WWWEQ5Z3j0FWygnqC4N09zETtCulsn8OPa5xm/s4n5Mng54fLL0CxzhlGOFaDKZLHxD20iaqbKKF60BQF/1RrpgxxRQeo6hsrduqLQdOW4abv6spEQTeFSj3p4XbLWuuI05Jh7CUnwajLoiqiPUs8omnWmdLHdQ7AKyd+vGVBu4YGstAJF/bFAl+gxNYI3GkEqu4HkSKcoVwg6HMXk7kvy2uvM1OV0N7mbhSbH/XTDokarECyQmH2m0jJ4EzQjvKi+YoJpeZTIZhqYOntNxOEqNWujN15qh7Desf61WLwAoz8MY4Lz19PbF7ZGNFD1Xl/JqE5O7/uhv+KJpsbJszLK53AF76z/dRknxsFQzikbRkpOQTxZoPczG+9FtGO6bEKuYTZb++t4JFj+H4DPPUbwSPlMobbbTcYlDIt3HzNYF+Hbv89rDXAxNUzdsSdPFAY4DRzvCtd+U1oBM70YtcyOrd4t9vX5KzQjMIp5rr8bgNNFc76lxIcif/mDuVwExUSlAoEtgg75zD+aNy/u/dpIi06DtwjB3Em0d6kTdOsgkQ/UHXsD3QlPpT//JvWt+iuL6Ou5YSI6mnzgJ6Sgtc+q3Ittv0MO6zbf09HoQhM/5DCLNyE4lKM75sc/Xn2MrdZds8kfSi1VWayPK3z8+9I8adQUdqQga6ZGtrAaSmjDGR6OFv17WMIRPL+3b80oJ0GPz946ICFR4INi9Iz0XuHu0GLHDreJAspUtSPPosi35CPd8lD2RBWewm+7hvLJPdnPddP0zxMLyA8iBjyKVHdEZIaxxmXK88OI1x4ZWGNPs8rEKr1bmfkrP/G9wdbNqlK7eRgQ0skIR4o9lp6XYJUhiPfFRqLQUtrQKeNRMi+9uXKrhRBDc12eQ32itzsuOEzq24zn1RqXGphrMGpUgJO9AY/9U7vUyjXIs8D1VzbomF7VjXRUlk77QnMXlO9FFB8WmaQtYfXq7+lEj8bt/ZakDXK4vhs2B+Ab2ZGHP6M+4efj/yIOcH9uzluP5Jda5Afi5VAQsy2iF4If+aJGz22I3bGs9uKYjs5VjCOdOhFY5+658k/dUmW8ylI93vibK3Naj2QyyUBSFBYKPcwKI7p+SJZUDbeYTHuAORWyiO+0BgsQS/bKtWNfNaM/vccEDOvE9LJaaAqe7v8aqDIB+1GpL7ncH4iDrHH230+tkVQ4KTLhliUe4YZNjZjOuMfvOGCuPDNFlZRPxMrLO+P2Vd8GKdg0yRWvKZ/MdPOeyVmND/bYv3mH7f+Iz3ydxZZQrosyzd+m176MYxlEN6n2aF9V8Aggfbl5Z8FSlt0dfzHcrYwYW0qVvOxGNwBqlu9v63GDW/6MuveAAd5yKra0jvAYDgxLL1xWTA/TmUXBm2Pm+Slze72GgY3YRaf4dDiY+fRIbTA/WNNy2Wng6h/l4aquGZpF0xkL4pUBYbuhfAm7y2BX/NXngigr7THksys99j67E4v+NexXwtCS0ooRoG1EHvbLn+HRY+Br5939x1tnHJWA2V7IhPizqrinmRmZzsScphZSpPih0eeyCECM1ot+M67PpVkMof0dR+3Nki0ZnU+MFNXRbUZWumweCuAajDiz+z/9Ja2Gz+MuqDq2JkbT2tmCdSn/ZTQJkFFUGaGYtpni+ELrDze1Od4UXh4WdhG5wnArXYH7unz+OfWaLm6Wv1rDMizVnZnh+gxsp1vjcnBdKvl930neLhW/Xk9cztUf2ZFa0GnR1yND+wWz+dWruxR1RvdoiMsHHy15Ju2+QcVgmIovYu3DtPx0HlfPrYo0oZ4OkOAAl+d5w1sh159JzuU1z9CZvqGuhYI09srYi/XEmaNoUSe82e3Z25YoaUqDABfT9qkdEHOZOmD+9rlnZI2hEDD3cNRmyS+sJi/dWFRMXnkQvg5P4bOXmaTAs7Trnp0Lj/k9zWeTbFXrAH3XNkwmOREM0PbnhfS2hgPq1CTQ0AsfuT45Vdys4nZ/LSaLHNxEHh9Es3I6aNjsFRCg9VDDZgNTw9SjjnRA+nc/2rcV0oStfA8hSyqcFzjUdpwDMw2IIiZHnFa0FfGTpV4OiUsMPO/gpe150Wu3xBSoRwYeHZjToqLYc/LGjEWyFHEt4QDRPhT0RiwYn5C8uuWXVmWB+b07FYtdNnWLSnVFD2/ZFKPE5irx7hDEnlRF2ZiEv4N+bHgnK1o87E6cCtoM4D449irGQ0Hc4rPByJOurJz9HBiIqNI3YH6/QzXlEqj/Z3WB+Aa92LOVhEgW/l3lyBjMd8KDZMU0pceZjQaJFdQnShtyvBYjsVSes3MO5dGDWgKSOOkbus7AeydOWKKxC25zo0lDiXyt0cMBqzAjdBFe8/6b8d1zwhXS/sSSYHh8syTA6ChrNLWheltfRtL9B8Diz6aICd5ezZn/CeHHXd9+I4PhKsOsaXebFMlYCYvjoahEJBbFvdH/6z8+1hEhqMAtPyweRe4WrJOM/KXK0gZjhuXBrhPJ3q6GHNiiOuzyoe62gr5dtlB0R4rhMX/5MDfDFswuOO5wG2H+3ryj+s40STZB62HZg5G8DidKA5xJ1rqqurzNncj1ZfwxQ+Vs5WXlbxsKNfinNwVqh0Or8Qts7PK54FJXTjqp2Ibj7ksNGD0tJTIRq7JK4hbIbq+o2zwd3jjRpqVIeSBwVOuoxY1MLvW63AZJVPOvRqNVP4MOo0RAtyfk4MnB7iJbGI0j/J+CJa5UqsBdqiQ1eUKg4G8+36iLbxe5KYgxIL4P9928ec+A54R2b1FDI8TUZUStwKbR2LqSoPalTE7Xt7n1pDT07VgrKVwIn/nM2T/51KU/g4j7pF1XqaxbpGKjuBV+68TPKQ3AI/sHKg5FWoNsf8qUylPXZ2d1LV6LvO3Ji9IwFhysDbCK+mUM95f+1LzRukdm81ZpzBZFzibpuXQR8IbLM5MjdRJRnQE2zXij4RumDQVOXJr+Cy1aY1poRijDqdzZ/6jWP1D8YEtHlgAUTew+llgFOz/88h9wNYEBljb2FBjKlWwolsrtBwRC9ylJm0zCrpmLp0VDpirnZ1OLIhCFYrqTDKcfCl6ydNr9zV2ByLjJaTadGc4OK6p95MeY88k0g4epE3lm04aU9ffQU4LnkVOI3mGpJMOoKn0hO9vlS9oRtac+D+zpZHmoK+HC97/NZN9confX8WGSEaFneyUbPRwrcdVWMfAqf3bXRfWzlMum3KV96yU9XeSdifJW2nBEkF9siNExDYDQLVKCnIaaT5NrjICVcwIeQf8ZuqNO1qdtm2LemRYSx/bu+gSX6SIduQ5zrSngTpb50fyo0TvuOkTq7e2p/dpOwFwlISvnskJcx8eNz5dUQ8f+MgEzhmQiwtBTJtnrYft/J3lCvszHr5q01ctOXxbEn1K/lJezzC/6H089+OLFsICiUAQsyKydG+46wWF7bGNo8jD7fiv24ALvTiwbY71dDZ0NdvM3gVxS/0fSnmVGk2IRTUgWW7bR1eAKPtCjw88CGnuZ/Mc4hdxD/enxL8tBolZ0jtL5PF1/nSfZ1cH/yk2hpXu1yDNCH0c+6n7ji4FkcxyNpklnRV8yJj2w3i9KW1/fwvihu/RCKPPftoMlPuzg1ri9Q3yPYBTr/4AEKtM2bJW+vv0SmtCMTGkmuzThYgTBTPIsYUuEU8TR2nC8EcsUAXTKcHKMKBXpgzVjcz1X2AkpH9yigwTlbo7Line truncated
|
||||||
|
<rect width="800" height="64" fill="#fbf8ef" />
|
||||||
|
<g fill="#082f55" font-family="Arial, sans-serif" font-size="25" font-weight="700" text-anchor="middle" direction="rtl">
|
||||||
|
<text x="132" y="43">المُقدِّمة</text>
|
||||||
|
<text x="400" y="43">السلوك</text>
|
||||||
|
<text x="668" y="43">النتيجة اللاحقة</text>
|
||||||
|
</g>
|
||||||
|
<g fill="none" stroke="#082f55" stroke-linecap="round" stroke-linejoin="round" stroke-width="4">
|
||||||
|
<path d="M235 37h60m-12-11 12 11-12 11" />
|
||||||
|
<path d="M507 37h57m-12-11 12 11-12 11" />
|
||||||
|
</g>
|
||||||
|
<rect x="381" y="190" width="50" height="24" rx="3" fill="#fff" opacity=".98" />
|
||||||
|
<text x="406" y="206" fill="#1f2933" font-family="Arial, sans-serif" font-size="9" text-anchor="middle" direction="rtl">المزيد</text>
|
||||||
|
<rect x="611" y="190" width="65" height="24" rx="3" fill="#fff" opacity=".98" />
|
||||||
|
<text x="643" y="205" fill="#1f2933" font-family="Arial, sans-serif" font-size="7" text-anchor="middle" direction="rtl">دقيقتان إضافيتان</text>
|
||||||
|
<rect x="531" y="351" width="255" height="91" rx="12" fill="#e8efe8" />
|
||||||
|
<g fill="#082f55" font-family="Arial, sans-serif" font-size="14" direction="rtl" text-anchor="middle">
|
||||||
|
<text x="658" y="374">◷ حصلتَ على دقيقتين إضافيتين.</text>
|
||||||
|
<text x="658" y="399">← ثم ينتهي الوقت.</text>
|
||||||
|
<text x="658" y="424">▣ تستمر خطة «أولًا/ثم».</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 9.5 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 569 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 553 KiB |